File: //opt/af/metric_agents.py
import os
import logging
import pandas as pd
import numpy as np
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools import tool
from agno.team import Team
from src.database.operations import execute_select_query # Für Datenbankzugriff
from datetime import datetime, timedelta
import plotly.graph_objects as go
# Konfiguriere Logging (einfach)
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
# Import CurrencyConverter for historical currency rates
try:
from currency_converter import CurrencyConverter
# Initialize once to avoid overhead on each conversion
currency_converter = CurrencyConverter(fallback_on_missing_rate=True, fallback_on_wrong_date=True)
log.info("CurrencyConverter successfully imported and initialized.")
currency_converter_available = True
except ImportError as e:
log.warning(f"CurrencyConverter could not be imported: {e}. Please install using 'pip install currencyconverter'")
currency_converter_available = False
except Exception as e:
log.error(f"Error initializing CurrencyConverter: {e}")
currency_converter_available = False
# --- Platzhalter-Import für den CalendarAgent ---
# Annahme: CalendarAgent ist eine Klasse in src/calendar_agent/agent.py
try:
# Passe den Pfad ggf. an deine Projektstruktur an
from src.calendar_agent.agent import CalendarAgent
# Instanziiere den Kalender-Agenten
calendar_agent_instance = CalendarAgent().agent # Annahme: .agent gibt die AgnoAgent-Instanz zurück
log.info("CalendarAgent successfully imported and instantiated.")
except ImportError as e:
log.warning(f"CalendarAgent could not be imported: {e}. Add it manually.")
calendar_agent_instance = None
except Exception as e:
log.error(f"Error instantiating CalendarAgent: {e}")
calendar_agent_instance = None
# Import YFinanceTools
try:
from agno.tools.yfinance import YFinanceTools
log.info("YFinanceTools successfully imported.")
yfinance_available = True
except ImportError as e:
log.warning(f"YFinanceTools could not be imported: {e}. Real-time market data will not be available.")
yfinance_available = False
# Import our new forex agents
forex_agents_available = False
try:
from src.ai.forex_agents import (
FXSeerLSTMAgent,
TrendSightTAAgent,
MomentumMoverAgent,
MeanReversionScoutAgent
)
forex_agents_available = True
log.info("Forex trading agents successfully imported.")
except ImportError as e:
log.warning(f"Forex trading agents could not be imported: {e}")
# --- Neues Tool für historische Währungskurse mit CurrencyConverter ---
@tool
def get_historical_exchange_rate(amount: float, from_currency: str, to_currency: str = "EUR", date_str: str = None) -> str:
"""
Gets historical exchange rate between currencies using the European Central Bank data.
Args:
amount (float): Amount to convert
from_currency (str): Source currency code (e.g., 'USD', 'EUR', 'GBP')
to_currency (str): Target currency code (default 'EUR')
date_str (str, optional): Date in YYYY-MM-DD format. If None, uses the latest available rate.
Returns:
str: Conversion result with details formatted as Markdown
"""
if not currency_converter_available:
return "Error: CurrencyConverter package not available. Please install with 'pip install currencyconverter'"
try:
# Parse date if provided
conversion_date = None
if date_str:
try:
conversion_date = datetime.strptime(date_str, "%Y-%m-%d").date()
except ValueError:
return f"Error: Invalid date format. Please use YYYY-MM-DD format."
# Get currency bounds to check availability
if from_currency not in currency_converter.currencies:
return f"Error: '{from_currency}' is not a supported currency. Supported currencies are: {', '.join(sorted(currency_converter.currencies))}"
if to_currency not in currency_converter.currencies:
return f"Error: '{to_currency}' is not a supported currency. Supported currencies are: {', '.join(sorted(currency_converter.currencies))}"
# Get bounds for the currencies
from_bounds = currency_converter.bounds[from_currency]
to_bounds = currency_converter.bounds[to_currency]
# Perform conversion
if conversion_date:
converted_amount = currency_converter.convert(amount, from_currency, to_currency, date=conversion_date)
else:
converted_amount = currency_converter.convert(amount, from_currency, to_currency)
conversion_date = from_bounds[1] # Use latest available date
# Format result with bounds information
from_start, from_end = from_bounds
to_start, to_end = to_bounds
result = f"""
## Historical Exchange Rate
**{amount:.2f} {from_currency} = {converted_amount:.4f} {to_currency}** on {conversion_date}
### Data Details
- Source: European Central Bank
- {from_currency} data available from {from_start} to {from_end}
- {to_currency} data available from {to_start} to {to_end}
"""
# Check if we used fallback methods
if hasattr(currency_converter, '_fallback_on_missing_rate') and currency_converter._fallback_on_missing_rate:
result += "- Note: A fallback method was used for missing rate data (linear interpolation)\n"
if hasattr(currency_converter, '_fallback_on_wrong_date') and currency_converter._fallback_on_wrong_date:
result += "- Note: Requested date was outside available data range, used nearest available date\n"
return result
except Exception as e:
log.error(f"Error getting historical exchange rate: {e}", exc_info=True)
return f"Error converting currency: {str(e)}"
# --- Generalisiertes Tool zum Lesen von Metrik-Dateien ---
@tool
def read_metric_file(filename: str, sub_directory: str) -> str:
"""
Reads a specific metric CSV file from a specified subdirectory.
Args:
filename (str): The name of the CSV file (e.g. 'bias_USDJPY_1_360.csv').
sub_directory (str): The subdirectory below DATA_DIR (e.g. 'afdata', 'volatility').
Returns:
str: The content of the CSV file as a formatted Markdown table or an error message.
"""
base_dir = os.getenv('DATA_DIR', 'data') # Base directory from environment variable or 'data'
filepath = os.path.join(base_dir, sub_directory, filename)
log.info(f"Attempting to read metric file: {filepath}")
if not os.path.exists(filepath):
log.warning(f"File not found: {filepath}")
return f"Error: The requested data file in '{sub_directory}' could not be found."
try:
# Read the CSV file
df = pd.read_csv(filepath)
# Convert to Markdown
markdown_table = df.to_markdown(index=False)
# Get display name without showing the filename
# Extract type and timeframe from filename for better display
display_name = "Data"
if "pivot" in filename.lower():
display_name = "Pivot Points Analysis"
if "_1_" in filename:
display_name += " (Short-term)"
elif "_2_" in filename:
display_name += " (Medium-term)"
elif "_3_" in filename:
display_name += " (Long-term)"
elif "bias" in filename.lower():
parts = filename.replace('.csv', '').split('_')
if len(parts) > 1:
currency = parts[1]
display_name = f"{currency} Bias Analysis"
elif "tech" in filename.lower():
parts = filename.replace('.csv', '').split('_')
if len(parts) > 1:
currency = parts[1]
display_name = f"{currency} Technical Indicators"
log.info(f"File {filename} successfully read and converted to Markdown.")
return f"## {display_name}\n\n{markdown_table}"
except pd.errors.EmptyDataError:
log.warning(f"File is empty: {filepath}")
return "Note: The requested data file is empty."
except Exception as e:
log.error(f"Error reading CSV file {filepath}: {e}", exc_info=True)
return f"Error reading the requested data: {str(e)}"
# --- Spezialisiertes Tool für Volatilitätsdaten ---
@tool
def find_currency_volatility(currency_code: str) -> str:
"""
Searches for volatility data for a specific currency in all available volatility files.
Args:
currency_code (str): The currency code (e.g. 'USD', 'EUR', 'AUD').
Returns:
str: The volatility data found for the currency, formatted as a Markdown table.
"""
log.info(f"Searching for volatility data for currency: {currency_code}")
# Normalize the currency code for search (uppercase)
currency = currency_code.upper()
# Base directory for data
base_dir = os.getenv('DATA_DIR', 'data')
afdata_dir = os.path.join(base_dir, 'afdata')
if not os.path.exists(afdata_dir):
return f"Error: Directory '{afdata_dir}' not found."
# Find all volatility files
volatility_files = []
for filename in os.listdir(afdata_dir):
if filename.startswith('volatility_') or filename.startswith('voltable_'):
volatility_files.append(os.path.join(afdata_dir, filename))
if not volatility_files:
return "No volatility files found."
log.info(f"Found volatility files: {volatility_files}")
# Collect results
results = []
# Search each file for the currency code
for file_path in volatility_files:
file_name = os.path.basename(file_path)
log.info(f"Searching {file_name} for {currency}")
try:
# Read the CSV file
df = pd.read_csv(file_path)
# Search for rows containing the currency
# Check the first column, which typically contains the currency name
if df.shape[1] > 0 and df.iloc[:, 0].dtype == 'object': # First column is text
currency_rows = df[df.iloc[:, 0].str.contains(currency, na=False)]
if not currency_rows.empty:
# Add the found data with file description
parts = file_name.replace('.csv', '').split('_')
timeframe = "4H" if parts[2] == "0" else f"{parts[2]}M"
results.append({
'file': file_name, # Keep for logs, not for output
'timeframe': timeframe,
'description': f"Volatility ({timeframe})",
'data': currency_rows.to_markdown(index=False)
})
except Exception as e:
log.error(f"Error searching {file_name}: {e}", exc_info=True)
if not results:
return f"No volatility data found for {currency}."
# Format the results as Markdown
markdown_output = f"## Volatility Data for {currency}\n\n"
for result in results:
# Only show the description with the timeframe, without the filename
markdown_output += f"### {result['description']}\n\n"
markdown_output += result['data'] + "\n\n"
# Add an explanation
markdown_output += """
#### Explanation of Columns:
- **BB Up/BB Low**: Upper/Lower Bollinger Band
- **BB Width**: Width of the Bollinger Band (measure of volatility)
- **Last Column**: Relative volatility (BB Width / Current Price)
The higher the BB Width and relative volatility, the more volatile the currency pair.
"""
return markdown_output
# --- Neues Tool zum Generieren eines Forex-Charts für bestimmte Zeiträume ---
@tool
def generate_forex_chart(currency_pair: str, start_date: str, end_date: str = None) -> str:
"""
Generates a forex chart for a specific currency pair and date range.
Args:
currency_pair (str): The currency pair (e.g., 'EUR/USD', 'GBP/JPY')
start_date (str): Start date in YYYY-MM-DD format
end_date (str): End date in YYYY-MM-DD format (defaults to current date if not specified)
Returns:
str: A description of the chart and the exchange rate data
"""
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
log.info(f"Generating forex chart for {currency_pair} from {start_date} to {end_date}")
# Process the currency pair to ensure consistent format
formatted_pair = currency_pair.upper().replace("/", "")
if len(formatted_pair) != 6:
return f"Error: Invalid currency pair format. Please use format like 'EUR/USD'."
# Set default end date if not provided
if not end_date:
end_date = datetime.now().strftime('%Y-%m-%d')
try:
# Parse dates
start = pd.to_datetime(start_date)
end = pd.to_datetime(end_date)
# Generate dates (excluding weekends)
date_range = pd.date_range(start=start, end=end, freq='D')
dates = date_range[date_range.dayofweek < 5] # 0=Monday, 4=Friday
# Dictionary to store the rates
exchange_rates = {}
# Check if we can use CurrencyConverter for real data
if 'currency_converter_available' in globals() and currency_converter_available:
base_currency = formatted_pair[:3]
quote_currency = formatted_pair[3:6]
# Get real exchange rate data
for date in dates:
try:
# Convert 1 unit of base to quote
rate = currency_converter.convert(1.0, base_currency, quote_currency, date=date.date())
exchange_rates[date.strftime('%Y-%m-%d')] = rate
except Exception as e:
log.warning(f"Couldn't get rate for {date.date()}: {e}")
# Use last available rate if we have one
if exchange_rates:
last_date = list(exchange_rates.keys())[-1]
exchange_rates[date.strftime('%Y-%m-%d')] = exchange_rates[last_date]
# If we couldn't get any real data, generate synthetic data
if not exchange_rates:
log.info("No real exchange rate data available, generating synthetic data")
# Base price depends on the currency pair
base_price = 1.0
if formatted_pair == 'EURUSD':
base_price = 1.07
elif formatted_pair == 'USDJPY':
base_price = 155.0
elif formatted_pair == 'GBPUSD':
base_price = 1.26
# Generate synthetic rates with a slight random walk
np.random.seed(42) # For reproducibility
daily_volatility = 0.005
current_rate = base_price
for date in dates:
date_str = date.strftime('%Y-%m-%d')
change = np.random.normal(0, daily_volatility) * current_rate
current_rate += change
exchange_rates[date_str] = current_rate
# Format for display - use more readable date format
formatted_rates = {}
for date_str, rate in exchange_rates.items():
# Convert to more readable format
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
readable_date = date_obj.strftime('%B %d, %Y')
formatted_rates[readable_date] = rate
# Calculate statistics
rates_list = list(exchange_rates.values())
avg_rate = sum(rates_list) / len(rates_list)
min_rate = min(rates_list)
min_date = list(formatted_rates.keys())[list(exchange_rates.values()).index(min_rate)]
max_rate = max(rates_list)
max_date = list(formatted_rates.keys())[list(exchange_rates.values()).index(max_rate)]
# Create a simple text representation of the data first
rate_text = "Daily Exchange Rates:\n\n"
for date, rate in formatted_rates.items():
rate_text += f"{date}: {rate:.4f}\n"
# Format the currency pair for display
display_pair = f"{formatted_pair[:3]}/{formatted_pair[3:6]}"
# Prepare the response with both simple text and markdown table format
response = f"""
## {display_pair} Exchange Rates ({start_date} to {end_date})
### Key Statistics
- **Average Rate:** {avg_rate:.4f}
- **Lowest Rate:** {min_rate:.4f} (on {min_date})
- **Highest Rate:** {max_rate:.4f} (on {max_date})
### Historical Exchange Rates
"""
# Add a simple text table that's guaranteed to display
response += rate_text
# Add the markdown table format as well
response += "\n\n### Data Table\n\n"
response += "| Date | Exchange Rate |\n"
response += "|------|---------------|\n"
for date, rate in formatted_rates.items():
response += f"| {date} | {rate:.4f} |\n"
response += "\n*Note: This data represents the historical exchange rates for {display_pair} during the requested period.*"
return response
except Exception as e:
log.error(f"Error generating forex data: {e}", exc_info=True)
return f"Error processing the request for {currency_pair} exchange rates: {str(e)}"
# --- Neues Tool für den Zugriff auf historische WordPress Daten ---
@tool
def query_wordpress_posts(search_terms: str = None, limit: int = 10, post_type: str = 'post') -> str:
"""
Searches historical data for content containing specific search terms.
Args:
search_terms (str, optional): The search terms to look for in titles or content.
limit (int, optional): The maximum number of results to return. Default is 10.
post_type (str, optional): The content type (e.g. 'post', 'page'). Default is 'post'.
Returns:
str: A Markdown-formatted list of found information with title, date, and excerpt.
"""
log.info(f"Searching for historical data with terms: {search_terms}")
if not search_terms:
return "No search terms provided. Please specify what you're looking for."
# Prepare multiple search patterns for financial data
search_patterns = []
# First add the original search term
search_terms_clean = search_terms.replace("'", "").replace('"', "")
search_patterns.append(f"%{search_terms_clean}%")
# For price queries, generate variations
if any(currency in search_terms_clean.upper() for currency in ['EUR', 'USD', 'JPY', 'GBP', 'AUD', 'NZD', 'CAD', 'CHF']):
# Extract potential price values using regex
import re
price_matches = re.findall(r'\d+[,.]?\d*', search_terms_clean)
currency_matches = re.findall(r'[A-Z]{3}\/[A-Z]{3}|[A-Z]{3}', search_terms_clean.upper())
if price_matches and currency_matches:
# For each price format, create variations
for price in price_matches:
# Convert commas to dots and vice versa
price_dot = price.replace(',', '.')
price_comma = price.replace('.', ',')
# For each currency pair format, create variations
for curr in currency_matches:
if '/' in curr:
currency_pair = curr
currency_pair_noslash = curr.replace('/', '')
else:
# Try to pair with another currency if this is a single currency code
for curr2 in currency_matches:
if curr != curr2:
currency_pair = f"{curr}/{curr2}"
currency_pair_noslash = f"{curr}{curr2}"
search_patterns.append(f"%{currency_pair}%{price_dot}%")
search_patterns.append(f"%{currency_pair}%{price_comma}%")
search_patterns.append(f"%{price_dot}%{currency_pair}%")
search_patterns.append(f"%{price_comma}%{currency_pair}%")
search_patterns.append(f"%{currency_pair_noslash}%{price_dot}%")
search_patterns.append(f"%{currency_pair_noslash}%{price_comma}%")
# Direct query on the awp_posts table
query = """
SELECT ID, post_title, post_date, post_content, post_excerpt, post_type, guid
FROM awp_posts
WHERE post_status = 'publish' AND post_type = %s
"""
# Parameters for the query
params = [post_type]
# Build the WHERE clause with all search patterns
if search_patterns:
or_clauses = []
for pattern in search_patterns:
or_clauses.append("(post_title LIKE %s OR post_content LIKE %s)")
params.extend([pattern, pattern])
if or_clauses:
query += " AND (" + " OR ".join(or_clauses) + ")"
else:
# If no patterns were created, use the original search term
query += " AND (post_title LIKE %s OR post_content LIKE %s)"
params.extend([f"%{search_terms_clean}%", f"%{search_terms_clean}%"])
# Limit the number of results
query += " ORDER BY post_date DESC LIMIT %s"
params.append(min(limit, 50)) # Maximum 50 entries
try:
# Execute the query
results = execute_select_query(query, tuple(params), return_type='dict')
if not results or len(results) == 0:
# If no results, try a simpler query with just the numbers or currency pairs
log.info("No results found with complex search. Trying simpler search...")
simple_query = """
SELECT ID, post_title, post_date, post_content, post_excerpt, post_type, guid
FROM awp_posts
WHERE post_status = 'publish' AND post_type = %s AND
(post_title LIKE %s OR post_content LIKE %s)
ORDER BY post_date DESC LIMIT %s
"""
# Extract just numbers or currency pairs for the simpler search
simple_terms = []
if price_matches:
for price in price_matches:
simple_terms.append(f"%{price}%")
if currency_matches:
for curr in currency_matches:
simple_terms.append(f"%{curr}%")
all_simple_results = []
for term in simple_terms:
simple_params = [post_type, term, term, min(limit, 20)]
term_results = execute_select_query(simple_query, tuple(simple_params), return_type='dict')
if term_results:
all_simple_results.extend(term_results)
if all_simple_results:
results = all_simple_results[:min(limit, 50)]
if not results or len(results) == 0:
return f"No specific historical information found about '{search_terms_clean}'. I can provide general information on this topic, but don't have specific historical data points at this time."
# Format the results as Markdown
markdown_output = f"### Historical Data for '{search_terms_clean}' ({len(results)} results)\n\n"
for post in results:
post_id = post.get('ID', 'No ID')
title = post.get('post_title', 'No Title')
date = post.get('post_date', 'No Date')
# Extract an excerpt from the content (maximum 300 characters)
content = post.get('post_content', '')
excerpt = post.get('post_excerpt', '')
if not excerpt and content:
# Remove HTML tags for a cleaner excerpt
content_clean = content.replace('<p>', ' ').replace('</p>', ' ').replace('<br>', ' ')
excerpt = content_clean[:300] + ('...' if len(content_clean) > 300 else '')
# Add the post to the Markdown output
markdown_output += f"#### {title}\n"
markdown_output += f"**Date:** {date}\n\n"
markdown_output += f"{excerpt}\n\n"
# Add the URL if available
if post.get('guid'):
markdown_output += f"[Source]({post.get('guid')})\n\n"
markdown_output += "---\n\n"
return markdown_output
except Exception as e:
error_msg = f"Error retrieving historical data: {str(e)}"
log.error(error_msg, exc_info=True)
return error_msg
# --- Angepasste und neue spezialisierte Metrik-Agenten ---
# Agent für Marktdaten (nutzt jetzt read_metric_file)
market_agent = Agent(
name="Market Agent",
role="Analyzes general market data and trends from various files in the 'afdata' directory to provide comprehensive market insights.",
model=OpenAIChat(id="gpt-4o"),
tools=[read_metric_file], # Uses the generic tool
instructions=[
"You are a financial market analyst specializing in providing current market insights based on available data.",
"To analyze current market conditions, follow this strategy:",
"1. First, check pivot files (pivots_*.csv) in the 'afdata' directory which contain aggregated market data.",
"2. Next, examine individual currency pair files with naming patterns like 'EURUSD_240_WP.csv' or 'GBPJPY_D_MP.csv'.",
"3. Focus on the most recent data entries in these files to identify current trends.",
"4. When analyzing market conditions:",
" - Look at multiple timeframes (from 10M to D (daily)) to identify short and long-term trends",
" - Compare data across major currency pairs (EUR/USD, GBP/USD, USD/JPY, etc.)",
" - Identify market patterns, pivot points, and key support/resistance levels",
" - Note anomalies or significant changes from previous data points",
"5. For specific market questions, prioritize these files in the 'afdata' directory:",
" - pivots_*.csv files for broad market pivot information",
" - Currency pair files with '_MP.csv' suffix for major pivot points",
" - Currency pair files with '_WP.csv' suffix for weekly pivots",
" - Currency pair files with '_DP.csv' suffix for daily pivots",
"6. Present your analysis in a clear, structured format with key insights highlighted.",
"7. Use read_metric_file with sub_directory='afdata' and the appropriate filename.",
"Example: To analyze EUR/USD trend, check both 'EURUSD_D_MP.csv' and 'pivots_1_240.csv' for comprehensive insights.",
],
show_tool_calls=True,
markdown=True,
)
# Agent für Volatilitätsdaten (verbessert mit spezialisiertem Tool)
volatility_agent = Agent(
name="Volatility Agent",
role="Analyzes volatility metrics and tables in the 'afdata' directory, especially for specific currencies and markets.",
model=OpenAIChat(id="gpt-4o"),
tools=[read_metric_file, find_currency_volatility], # Both tools available
instructions=[
"You are a specialist in financial market volatility.",
"For queries about specific currencies (e.g., AUD, USD, EUR) use find_currency_volatility() first.",
"For specific files, you can use read_metric_file.",
"All volatility files are in the 'afdata' directory, not in the 'volatility' directory.",
"Available volatility files have the prefixes 'volatility_' and 'voltable_'.",
"Explain the meaning of volatility metrics and interpret them in the context of the current market situation.",
"For queries like 'What is the volatility of AUD?' use find_currency_volatility('AUD').",
"For file queries, use read_metric_file(filename='volatility_1_240.csv', sub_directory='afdata').",
],
show_tool_calls=True,
markdown=True,
)
# Agent für Pivot-Punkte (angepasst und nutzt jetzt read_metric_file)
pivot_agent = Agent(
name="Pivot Agent",
role="Analyzes pivot points and technical levels from 'pivots' and 'afdata'.",
model=OpenAIChat(id="gpt-4o"),
tools=[read_metric_file],
instructions=[
"Identify relevant pivot levels.",
"Use read_metric_file for files in 'pivots' (e.g., 'pivots_1_240.csv').",
"Use read_metric_file for pivot files in 'afdata' (e.g., 'AUDCAD_30M_DP.csv', 'EURJPY_240_WP.csv', 'NZDJPY_D_MP.csv').",
"Specify the subdirectory ('pivots' or 'afdata') correctly.",
"Visualize the levels if possible (as a text description).",
],
show_tool_calls=True,
markdown=True,
)
# NEU: Agent für technische Indikatoren aus afdata
tech_indicator_agent = Agent(
name="Tech Indicator Agent",
role="Analyzes technical indicator files (tech_*.csv) from the 'afdata' directory.",
model=OpenAIChat(id="gpt-4o"),
tools=[read_metric_file],
instructions=[
"Focus on files that start with 'tech_' in the 'afdata' directory.",
"Use read_metric_file with sub_directory='afdata' and the specific filename (e.g., 'tech_AUDJPY_2_0.csv').",
"Interpret the technical indicators.",
],
show_tool_calls=True,
markdown=True,
)
# NEU: Agent für Markt-Bias aus afdata
bias_agent = Agent(
name="Bias Agent",
role="Analyzes market bias files (bias_*.csv) from the 'afdata' directory.",
model=OpenAIChat(id="gpt-4o"),
tools=[read_metric_file],
instructions=[
"Focus on files that start with 'bias_' in the 'afdata' directory.",
"Use read_metric_file with sub_directory='afdata' and the specific filename (e.g., 'bias_USDJPY_1_360.csv').",
"Explain the market bias evident from the data.",
],
show_tool_calls=True,
markdown=True,
)
# NEU: Agent für Heatmaps aus afdata
heatmap_agent = Agent(
name="Heatmap Agent",
role="Analyzes heatmap files (heat_map_*.csv) from the 'afdata' directory.",
model=OpenAIChat(id="gpt-4o"),
tools=[read_metric_file],
instructions=[
"Focus on files that start with 'heat_map_' in the 'afdata' directory.",
"Use read_metric_file with sub_directory='afdata' and the specific filename.",
"Describe the information shown in the heatmap.",
],
show_tool_calls=True,
markdown=True,
)
# Agent für CB-Daten (nutzt jetzt read_metric_file)
cb_agent = Agent(
name="CB Data Agent",
role="Analyzes data from the 'cb' directory.",
model=OpenAIChat(id="gpt-4o"),
tools=[read_metric_file], # Uses the generic tool
instructions=[
"Analyze the data in the 'cb' directory.",
"Use read_metric_file with sub_directory='cb' and the filename.",
],
show_tool_calls=True,
markdown=True,
)
# NEU: Agent für historische Währungskurse mit CurrencyConverter
currency_rates_agent = None
if currency_converter_available:
currency_rates_agent = Agent(
name="Currency Rates Agent",
role="Provides historical exchange rates between currencies using European Central Bank data",
model=OpenAIChat(id="gpt-4o"),
tools=[get_historical_exchange_rate],
instructions=[
"You are a financial data specialist providing historical currency exchange rates.",
"You provide historical exchange rates for 42 currencies against the Euro since 1999.",
"Always use the get_historical_exchange_rate tool to fetch accurate historical data.",
"When asked about exchange rates, always specify a date if possible.",
"For time periods, provide both rates at the beginning and end of the period.",
"For general queries without a date, use the most recent available rate.",
"If asked about currency movements, include percentage changes when comparing rates from different dates.",
"Always specify the date of the exchange rate in your response.",
"For missing data, explain that the data might be interpolated from nearby dates.",
"For dates before 1999, explain that Euro exchange rates are only available from January 4, 1999.",
"Be precise with currency codes: use standard 3-letter codes (EUR, USD, GBP, etc.)",
"You can convert any amount between any of the supported currencies on any date since 1999."
],
show_tool_calls=True,
markdown=True,
)
log.info("Currency Rates Agent created successfully.")
else:
log.warning("Currency Rates Agent not created due to missing CurrencyConverter package.")
# NEU: Agent für historische WordPress-Daten
historical_data_agent = Agent(
name="Historical Data Agent",
role="Analyzes historical data and provides additional context to current events.",
model=OpenAIChat(id="gpt-4o"),
tools=[query_wordpress_posts, generate_forex_chart, get_historical_exchange_rate] if currency_converter_available else [query_wordpress_posts, generate_forex_chart],
instructions=[
"You are a financial analyst assistant with access to historical articles and analyses.",
"IMPORTANT: NEVER mention the source of your data or your data access methods in your responses.",
"CRITICAL: NEVER reference external images or charts. Do not use URLs like 'example.com' or any other external sources.",
"When presenting data, ALWAYS use the direct output from the tools without adding references to external images.",
"Do not say things like 'see chart at URL' or 'refer to image' - only use the text and data from your tools.",
"RELATIVE TIME HANDLING - HIGHEST PRIORITY:",
"1. For ANY query containing relative time expressions like 'last month', 'last week', 'recent', 'lately', etc.:",
" a. FIRST determine today's date (assume current date unless otherwise specified)",
" b. CALCULATE the exact date range based on the relative time expression",
" c. ALWAYS use query_wordpress_posts tool with the calculated date range in your search terms",
" d. Do not skip this step or say you don't have access to the data",
"2. For 'last month' queries specifically:",
" a. Calculate the date range of the previous calendar month (e.g., if today is April 15, 2025, 'last month' means March 1-31, 2025)",
" b. Include the full month name and year in your search terms (e.g., 'March 2025')",
" c. Also include any specific topics mentioned (e.g., 'forex events March 2025')",
"3. For 'recent' or 'lately' queries:",
" a. Use the past 30 days as the default range",
" b. Include date markers like the month names in your search",
"4. CRITICAL: NEVER respond that you don't have access to the data without first trying the query_wordpress_posts tool",
" If the first search yields no results, try multiple alternative search terms before giving up",
"5. For forex event queries, search with multiple relevant terms:",
" a. 'forex events [time period]'",
" b. 'market impact [time period]'",
" c. 'currency movements [time period]'",
" d. Major currency pair names like 'EUR/USD', 'USD/JPY', etc.",
" e. Economic terms like 'interest rates', 'inflation', 'central bank'",
"CRITICAL WORKFLOW FOR CHART AND DATE RANGE QUERIES:",
"1. ALWAYS calculate the exact date range required, do not use default dates",
"2. For chart requests with relative time references (e.g., 'week prior to X'):",
" a. First identify the reference date (e.g., 'last day of Biden administration' = January 20, 2025)",
" b. Calculate the exact start and end dates (e.g., 'week prior' = January 13-19, 2025)",
" c. ALWAYS use these specific dates with the generate_forex_chart tool",
" d. TRIPLE CHECK that you're using the correct date range before generating the chart",
"3. Present the data directly from the tool response without adding URLs or external references",
"DATE CALCULATION EXAMPLES:",
"- 'Week prior to January 20, 2025' = January 13-19, 2025",
"- 'Month before March 15, 2024' = February 15-March 14, 2024",
"- 'First week of 2023' = January 1-7, 2023",
"- 'Last quarter of 2022' = October 1-December 31, 2022",
"- 'Last month from April 15, 2025' = March 1-31, 2025",
"DATE VALIDATION:",
"- Before calling any tool, explicitly calculate and verify the date range",
"- Format dates in ISO format (YYYY-MM-DD) when passing to tools",
"- Always include both start_date and end_date parameters in chart generation",
"- Verify that end_date is NEVER after start_date",
"- Check that date ranges are not excessively large (typical chart ranges: 7-90 days)",
"EXAMPLE REQUEST: 'Draw me a graph of EUR/USD for the week prior to the last day of the Biden administration'",
"CORRECT THOUGHT PROCESS:",
"1. 'Last day of Biden administration' = January 20, 2025",
"2. 'Week prior' = January 13-19, 2025",
"3. Call generate_forex_chart with:",
" - currency_pair='EUR/USD'",
" - start_date='2025-01-13'",
" - end_date='2025-01-19'",
"4. Present the output exactly as returned by the tool without referencing external images",
"EXAMPLE REQUEST: 'What major forex events impacted the markets last month?'",
"CORRECT THOUGHT PROCESS:",
"1. Determine today's date (e.g., April 15, 2025)",
"2. Calculate 'last month' = March 1-31, 2025",
"3. Call query_wordpress_posts with search terms 'forex events March 2025'",
"4. If no results, try alternative terms like 'market impact March 2025', 'currency movements March 2025', etc.",
"5. Summarize the relevant events found in the search results",
"CRITICAL WORKFLOW FOR ALL HISTORICAL QUERIES:",
"1. First, identify the specific date(s) or time period in the query",
"2. For date references like 'last day of Biden administration', translate to the specific date (January 20, 2025)",
"3. ALWAYS use query_wordpress_posts to search for information about that specific date and topic",
"4. If the search returns relevant information, summarize and present it",
"5. If no relevant information is found, THEN use the generate_forex_chart tool for price/chart queries",
"6. For exchange rate questions, use the get_historical_exchange_rate tool with the specific date",
"7. Only provide general information as a last resort when tools yield no specific data",
"When presented with ANY historical query (dates, prices, events, etc.), you MUST FIRST use query_wordpress_posts.",
"EXAMPLE: Query: 'What was EUR/USD on January 20, 2025?'",
"Action: Use query_wordpress_posts with search terms 'EUR/USD January 20 2025'",
"If no specific information is found, then use get_historical_exchange_rate(1.0, 'EUR', 'USD', '2025-01-20')",
"NEVER give a general response without first attempting to search for the specific information.",
"If a search doesn't return exact matches, try alternative search terms or date formats before giving up.",
"CRITICAL: When given information about the current date (e.g., 'Today's date is April 14, 2025'), treat that as the actual current date.",
"When processing queries with date references (like 'last month', 'recent', etc.), calculate these relative to the current date provided.",
"For example, if told 'Today's date is April 14, 2025' and asked 'What was EUR/USD on January 20, 2025?', treat January 20, 2025 as a past date.",
"If a query references a date that is in the future, even from the simulated current date, explain that data isn't available for future dates.",
"When a user asks for a chart or graph for a specific date range, use the generate_forex_chart tool.",
"For chart requests like 'Show me EUR/USD for the week before January 20, 2025', calculate the date range and use generate_forex_chart.",
"If a chart request specifies a time period like 'week', 'month', calculate the precise start and end dates for that period.",
"For example, 'the week prior to January 20, 2025' would be January 13-19, 2025.",
"For currency conversion queries, use get_historical_exchange_rate with the appropriate date.",
"EXAMPLE: 'What was 100 USD in EUR on March 15, 2020?'",
"Action: Use get_historical_exchange_rate(100, 'USD', 'EUR', '2020-03-15')",
"If no results are found for a query, simply provide general information about the topic without mentioning data sources.",
"For example, instead of saying 'No data found in the database', say 'I don't have specific historical details about this topic, but here's some general information...'",
"For price history questions (e.g., 'When was EUR/USD at 1.13?', 'Wann war EUR/USD bei 1,13?'):",
" - Extract the currency pair and price point",
" - Search using both formats: '1.13 EUR/USD', 'EUR/USD 1.13', etc.",
" - Try variations of the price (1.13, 1,13, 1.130, etc.)",
" - For currency pairs, search for both formats: 'EUR/USD' and 'EURUSD'",
"For date-based queries:",
" - Be flexible with date formats in your search",
" - Try different date ranges if specific dates don't yield results",
"For event-related queries:",
" - Search for the specific event name",
" - Also search for related terminology",
"When searching in non-English languages:",
" - First identify key search terms",
" - Translate search terms to English if needed",
" - Use both original and translated terms in separate searches",
],
show_tool_calls=True,
markdown=True,
)
# NEU: Agent für Real-time Marktdaten mit YFinance
realtime_market_agent = None
if yfinance_available:
realtime_market_agent = Agent(
name="Realtime Market Agent",
role="Provides real-time market data, stock prices, forex rates, and financial analysis using Yahoo Finance.",
model=OpenAIChat(id="gpt-4o"),
tools=[
YFinanceTools(
stock_price=True,
analyst_recommendations=True,
stock_fundamentals=True
)
],
instructions=[
"You are a financial market data specialist providing real-time data from Yahoo Finance.",
"ALWAYS use Yahoo Finance data for ANY market-related questions.",
"For market overview questions like 'How is the market today?':",
" - ALWAYS check major indices (^GSPC/S&P 500, ^DJI/Dow Jones, ^IXIC/NASDAQ)",
" - Include major forex pairs (EURUSD=X, USDJPY=X, GBPUSD=X)",
" - NEVER mention file names or data sources in your response",
"Analyze live market data for stocks, ETFs, indices, and forex pairs.",
"For forex analysis, use ticker formats like 'EURUSD=X', 'GBPUSD=X', etc.",
"For indices, use formats like '^DJI' (Dow Jones), '^GSPC' (S&P 500), '^IXIC' (NASDAQ).",
"When providing market analysis:",
" - Include current price, daily change, recent highs/lows",
" - Compare against relevant benchmarks",
" - Highlight significant price movements",
" - Note trading volume trends where applicable",
" - Provide context for market movements when possible",
"Format responses using markdown and tables for clarity.",
"For currency pairs, always use the '=X' suffix (e.g., 'EURUSD=X').",
"Remember to NEVER mention file names, tools, or data sources in your response.",
],
show_tool_calls=True,
markdown=True,
)
log.info("Realtime Market Agent created successfully.")
else:
log.warning("Real-time market data agent not created due to missing YFinanceTools.")
# --- Koordinator-Agent (Team) ---
# Stelle sicher, dass der CalendarAgent importiert wurde, bevor er zum Team hinzugefügt wird
team_members = [
market_agent,
volatility_agent,
pivot_agent,
tech_indicator_agent, # Neu
bias_agent, # Neu
heatmap_agent, # Neu
cb_agent,
historical_data_agent # NEU: Historical Data Agent hinzugefügt
]
# Add Currency Rates Agent if available
if currency_rates_agent:
team_members.append(currency_rates_agent)
log.info("Currency Rates Agent added to the team.")
# Add forex agents if available
if forex_agents_available:
try:
# Create forex agent instances
fxseer_lstm_agent = FXSeerLSTMAgent()
trendsight_ta_agent = TrendSightTAAgent()
momentum_mover_agent = MomentumMoverAgent()
mean_reversion_agent = MeanReversionScoutAgent()
# Add agents to team_members
team_members.append(fxseer_lstm_agent)
team_members.append(trendsight_ta_agent)
team_members.append(momentum_mover_agent)
team_members.append(mean_reversion_agent)
log.info("Forex agents successfully added to the team")
except Exception as e:
log.error(f"Error initializing forex agents: {e}", exc_info=True)
if calendar_agent_instance:
# Füge die tatsächliche AgnoAgent-Instanz hinzu
team_members.append(calendar_agent_instance)
log.info("CalendarAgent added to the team.")
else:
log.warning("CalendarAgent was not added to the team because it could not be loaded.")
# Add real-time market data agent if available
if realtime_market_agent:
team_members.append(realtime_market_agent)
log.info("Realtime Market Agent added to the team.")
# --- Integration of Data Query & Analysis Agent ---
try:
from src.ai.agent import AIAgent
data_query_agent = AIAgent()
# Create an Agno Agent wrapper for the AIAgent for proper team integration
from agno.agent import Agent as AgnoAgent
# Create a dedicated Visual Analysis Agent that uses the AIAgent
visual_analysis_agent = Agent(
name="Visual Analysis Agent",
role="Creates visual analysis, charts, and plots for currency pairs, stocks, and financial data.",
model=OpenAIChat(id="gpt-4o"),
tools=[], # Using AIAgent's process_query method
instructions=[
"You are a financial visualization specialist.",
"You handle ANY request for visual analysis, charts, plots, or graphs of financial data.",
"This includes currency pairs (EUR/USD, GBP/JPY, etc.), stocks, indices, and any financial instrument.",
"When used, forward the query directly to process the visualization request.",
"Your primary responsibility is to create visual charts using the create_plotly_chart tool.",
"ALWAYS respond with a visualization created by create_plotly_chart tool.",
"For currency pairs, do the following steps:",
"1. Query the database to get recent price data",
"2. Store the data with a suitable name",
"3. Use create_plotly_chart to generate a chart from that data",
"NEVER respond with just text descriptions of charts - always ensure the create_plotly_chart tool is used.",
],
show_tool_calls=True,
markdown=True,
)
# Override the process method to use AIAgent's process_query method
def visual_agent_wrapper(message, **kwargs):
log.info(f"Visual Analysis Agent processing: {message}")
try:
# Give explicit instructions to create a Plotly chart
enhanced_message = (
f"Create and return a Plotly chart visualization for {message}. "
f"You MUST use the create_plotly_chart tool to generate a chart. "
f"Step 1: Run a SQL query to get data for {message}. Store results with a descriptive name. "
f"Step 2: Use create_plotly_chart tool with the stored data to generate a visualization. "
f"Return ONLY the visualization without additional explanations."
)
result = data_query_agent.process_query(enhanced_message)
log.info(f"Visual Analysis Agent received result of length: {len(str(result))}")
# Check if result contains PLOTLY_JSON: marker
if isinstance(result, str) and "PLOTLY_JSON:" not in result:
log.warning("Visual Analysis Agent result doesn't contain PLOTLY_JSON marker")
# Try to recover with a more direct approach
direct_message = (
f"You must use create_plotly_chart for {message}. "
f"Return ONLY the visualization JSON with the PLOTLY_JSON: prefix."
)
result = data_query_agent.process_query(direct_message)
log.info(f"Second attempt result length: {len(str(result))}")
return result
except Exception as e:
log.error(f"Error in Visual Analysis Agent: {e}", exc_info=True)
return f"Error creating visual analysis: {str(e)}"
# Replace the process method
visual_analysis_agent.process = visual_agent_wrapper
# Add to team members
team_members.append(visual_analysis_agent)
log.info("Visual Analysis Agent added to the team.")
# Regular Data Query & Analysis Agent (without visualization focus)
data_analysis_agent = Agent(
name="Data Query & Analysis Agent",
role="Accesses databases and CSVs, manipulates datasets, and performs analysis.",
model=OpenAIChat(id="gpt-4o"),
tools=[], # No direct tools - using AIAgent's process_query method
instructions=[
"You are a data specialist who provides access to ActionForex databases and SFTP CSV files.",
"You can fetch, store, manipulate, and analyze data from multiple sources.",
"For data query, database access, and data manipulation requests, use this agent.",
"When used, simply forward the user's query exactly as received to the agent.",
"Do not attempt to rewrite, summarize, or modify the user's request in any way.",
"This agent can:",
"- Query MySQL databases",
"- Retrieve and process CSV files from SFTP",
"- Store data in memory for analysis",
"- Merge, filter, and manipulate datasets",
"For requests involving database queries or data manipulation, use this agent.",
],
show_tool_calls=True,
markdown=True,
)
# Override the process method to use our AIAgent's process_query method
def data_agent_wrapper(message, **kwargs):
log.info(f"Data Analysis Agent processing: {message}")
try:
result = data_query_agent.process_query(message)
log.info(f"Data Analysis Agent received result of length: {len(str(result))}")
return result
except Exception as e:
log.error(f"Error in Data Query Agent: {e}", exc_info=True)
return f"Error processing data query: {str(e)}"
# Replace the agent's process method with our wrapper
data_analysis_agent.process = data_agent_wrapper
# Add to team members
team_members.append(data_analysis_agent)
log.info("Data Query & Analysis Agent added to the team.")
except ImportError as e:
log.warning(f"Data and Visual Analysis Agents could not be imported: {e}")
except Exception as e:
log.error(f"Error integrating Data and Visual Analysis Agents: {e}", exc_info=True)
metric_team = Team(
mode="coordinate", # Coordinates requests to members
members=team_members, # Updated member list
model=OpenAIChat(id="gpt-4o"), # Model for the coordinator itself
success_criteria="The request was successfully delegated to the appropriate agent and their response was returned.",
instructions=[
"You are the coordinator for a team of financial data specialists.",
"VERY IMPORTANT: NEVER mention implementation details like databases, data sources, or technical infrastructure.",
"HIGHEST PRIORITY RULES:",
"1. For ANY general market questions (e.g., 'How is the market today?', 'How are the markets?', 'Wie ist der Markt heute?'), you MUST use ONLY the Realtime Market Agent.",
"2. For ANY current price inquiries (e.g., 'EUR/USD price', 'S&P 500 today'), you MUST use ONLY the Realtime Market Agent.",
"3. For ANY historical price or date questions (e.g., 'When was EUR/USD at 1.10?', 'Wann war der EUR/USD bei 1,13?'), you MUST use ONLY the Historical Data Agent.",
"4. For ANY questions about historical roles, backgrounds, or contexts of indicators (e.g., 'What is the historic role of Business NZ PSI?'), you MUST use ONLY the Historical Data Agent.",
"5. For ANY database queries or data manipulation requests, you MUST use ONLY the Data Query & Analysis Agent.",
"6. For ANY visual analysis, charts, plots, graphs, or any visualization of financial data, you MUST use ONLY the Visual Analysis Agent.",
"7. For ANY historical currency exchange rate queries (e.g., 'What was USD to EUR on January 10, 2020?'), you MUST use the Currency Rates Agent or Historical Data Agent.",
"8. For ANY forex price prediction requests (e.g., 'Predict EUR/USD for tomorrow', 'What's the forecast for GBP/JPY?'), you MUST use ONLY the FXSeer LSTM Agent.",
"9. For ANY technical analysis of forex pairs (e.g., 'Analyze EUR/USD with RSI', 'What do technical indicators show for GBP/USD?'), you MUST use ONLY the TrendSight TA Agent.",
"10. For ANY momentum-based trading ideas or currency pair ranking (e.g., 'Show top momentum forex pairs', 'Which currency pairs have the strongest momentum?'), you MUST use ONLY the Momentum Mover Agent.",
"11. For ANY mean reversion or price deviation analysis (e.g., 'Find mean reversion opportunities for EUR/USD', 'Is USD/JPY overbought or oversold?'), you MUST use ONLY the Mean Reversion Scout Agent.",
"Route other requests based on content strictly to the most appropriate agent:",
" - Visual analysis requests -> Visual Analysis Agent",
" - Charting/plotting/visualization -> Visual Analysis Agent",
" - Database queries -> Data Query & Analysis Agent",
" - CSV files from SFTP -> Data Query & Analysis Agent",
" - Calendar/Dates/Events -> CalendarAgent",
" - Specific stored market data files -> Market Agent ('afdata')",
" - Real-time market data/Live prices -> Realtime Market Agent",
" - Volatility or currency fluctuation queries -> Volatility Agent (e.g., 'AUD volatility', 'EUR volatility')",
" - Pivot points/levels -> Pivot Agent ('pivots', 'afdata/*_DP.csv', etc.)",
" - Technical indicators -> Tech Indicator Agent ('afdata/tech_*')",
" - Market bias/sentiment -> Bias Agent ('afdata/bias_*')",
" - Heatmaps -> Heatmap Agent ('afdata/heat_map_*')",
" - CB data -> CB Data Agent ('cb')",
" - Historical currency exchange rates -> Currency Rates Agent",
" - Historical data/context/background -> Historical Data Agent",
" - Forex price predictions -> FXSeer LSTM Agent",
" - Technical analysis of forex pairs -> TrendSight TA Agent",
" - Momentum-based forex trading ideas -> Momentum Mover Agent",
" - Mean reversion opportunities -> Mean Reversion Scout Agent",
"ALWAYS return the specialized agent's response directly and unchanged.",
"DO NOT summarize or rephrase the specialized agent's response.",
"DO NOT add any text mentioning which agent was used.",
"Important routing rules:",
"1. For general market questions in ANY language -> ONLY use Realtime Market Agent",
"2. For current price inquiries -> ONLY use Realtime Market Agent",
"3. For volatility questions about specific currencies -> use Volatility Agent",
"4. For technical analysis of stored data -> use the appropriate specialized agent",
"5. For historical context, background, or 'what is the role of' questions -> use Historical Data Agent",
"6. For questions containing 'wann' (when), 'früher' (earlier), 'historisch' (historical), 'zuletzt' (last time) -> use Historical Data Agent",
"7. For questions about specific price points in the past -> use Historical Data Agent",
"8. For questions about database queries, SQL, CSV files, data analysis -> use Data Query & Analysis Agent",
"9. For requests containing 'query', 'store', 'merge', 'filter', 'analyze data' -> use Data Query & Analysis Agent",
"10. For requests containing 'visual analysis', 'chart', 'plot', 'graph', 'visualization' -> use Visual Analysis Agent",
"11. For historical currency exchange rates -> use Currency Rates Agent",
"12. For requests containing 'prediction', 'forecast', 'predict' related to forex -> use FXSeer LSTM Agent",
"13. For requests for technical analysis of forex pairs -> use TrendSight TA Agent",
"14. For requests about momentum or strongest/weakest currency pairs -> use Momentum Mover Agent",
"15. For requests about mean reversion, overbought/oversold conditions -> use Mean Reversion Scout Agent",
"Language considerations:",
"- German 'wann' = 'when' in English - route to Historical Data Agent",
"- German 'zuletzt' = 'last time' in English - route to Historical Data Agent",
"- German questions about past events or prices -> route to Historical Data Agent",
"Route requests that require deeper analysis or historical information to the Historical Data Agent as well.",
"For questions about events like 'What are the impacts of X?' use both the relevant specialist agent and the Historical Data Agent for additional context.",
],
show_tool_calls=True,
markdown=True,
)
# --- Beispielhafte Nutzung (optional) ---
if __name__ == "__main__":
# Example: Ask a question to the team
print("Submitting request to the Metrics Team:")
# Example for volatility
# metric_team.print_response("Read the file 'voltable_1_60.csv' from afdata.", stream=True)
# Example for bias
# metric_team.print_response("What is the market bias for EUR/GBP according to 'bias_EURGBP_1_360.csv'?", stream=True)
# Example for calendar (if CalendarAgent was loaded)
if calendar_agent_instance:
metric_team.print_response("Show me today's events.", stream=True)
else:
print("\nCalendar Agent not available.")
# Example for historical data
# print("\n---")
# metric_team.print_response("What impacts can RBA Meeting Minutes have and what do historical data show about this?", stream=True)
# Note: To run this, you need the Agno library and dependencies:
# pip install agno openai pandas python-dotenv mysql-connector-python
# Set your OpenAI API Key as an environment variable: export OPENAI_API_KEY='YOUR_KEY'
# Optional: Create a .env file with DATA_DIR=/opt/af/data
pass # Remove print calls to avoid conflicts at startup