Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
97c7696
feat: refactor AI Hedge Fund Agent structure and add remote agent sup…
vcfgv Sep 8, 2025
f0615a7
fix: enhance error handling in GenericAgentExecutor and improve logging
vcfgv Sep 9, 2025
63a16cd
feat: implement remote agent configuration loading and connection han…
vcfgv Sep 9, 2025
5f1539b
feat: add example script for using RemoteConnections with remote agents
vcfgv Sep 9, 2025
400f030
feat: add agno as dependency
vcfgv Sep 9, 2025
ecb987f
feat: enhance remote agent demo with validated natural language to ba…
vcfgv Sep 9, 2025
8e37c90
fix lint and format
vcfgv Sep 9, 2025
ec5a7e7
feat: remove test_client.py as it is no longer needed
vcfgv Sep 9, 2025
63a7812
feat: disable default listener and push notifications in agent config…
vcfgv Sep 10, 2025
09cb17e
feat: rename send_message parameter `exhaustive` as `streaming`
vcfgv Sep 10, 2025
0c8bbd9
refactor: streamline error handling in AIHedgeFundAgent's stream method
vcfgv Sep 10, 2025
222de7b
feat: move example scripts for core agent functionality and remote co…
vcfgv Sep 10, 2025
43be7f2
feat: exclude examples from packaging
vcfgv Sep 10, 2025
3ea2c39
feat: add comprehensive README for ValueCell Agent Core with examples…
vcfgv Sep 10, 2025
a1e78e5
feat: implement get_agent_card_path utility function and update remot…
vcfgv Sep 10, 2025
34a9508
feat: add parse_host_port function to handle URL-like strings and upd…
vcfgv Sep 10, 2025
c827b99
refactor: update agent decorator usage to create_wrapped_agent and im…
vcfgv Sep 10, 2025
db90c16
doc: optimize BaseAgent stream docstring
vcfgv Sep 10, 2025
1e139f6
feat: enable listener by default in start_agent method
vcfgv Sep 10, 2025
b062dc1
fix format
vcfgv Sep 10, 2025
92b27ac
fix: update tickers description to use allowed_tickers set
vcfgv Sep 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions python/configs/agent_cards/hedge_fund_agent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "AIHedgeFundAgent",
"url": "http://localhost:10001/",
"enabled": true
}
5 changes: 5 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies = [
"tushare>=1.4.24",
"requests>=2.32.5",
"akshare>=1.17.44",
"agno>=1.8.2",
]

[project.optional-dependencies]
Expand Down Expand Up @@ -58,4 +59,8 @@ exclude = [
"**/third_party/**",
"tests/**",
"**/tests/**",
"examples/**",
"**/examples/**",
"docs/**",
"**/docs/**",
]
162 changes: 105 additions & 57 deletions python/third_party/ai-hedge-fund/adapter/__main__.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,115 @@
import asyncio
import json
import logging
import sys
from pathlib import Path

import click
import httpx
import uvicorn
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import (
BasePushNotificationSender,
InMemoryPushNotificationConfigStore,
InMemoryTaskStore,
from datetime import datetime
from typing import List

from agno.agent import Agent
from agno.models.openrouter import OpenRouter
from dateutil.relativedelta import relativedelta
from pydantic import BaseModel, Field, field_validator
from valuecell.core.agent.decorator import create_wrapped_agent
from valuecell.core.agent.types import BaseAgent

from src.main import run_hedge_fund
from src.utils.analysts import ANALYST_ORDER

allowed_analysts = set(
key for display_name, key in sorted(ANALYST_ORDER, key=lambda x: x[1])
)
from a2a.types import AgentCard
from adapter.agent_executor import VanillaAgentExecutor

logger = logging.getLogger(__name__)


@click.command()
@click.option('--host', 'host', default='localhost')
@click.option('--port', 'port', default=10001)
@click.option('--agent-card', 'agent_card_file')
def main(host, port, agent_card_file):
"""Starts an Agent server."""
try:
if not agent_card_file:
raise ValueError('Agent card is required')
with Path.open(agent_card_file) as file:
data = json.load(file)
agent_card = AgentCard(**data)

client = httpx.AsyncClient()
push_notification_config_store = InMemoryPushNotificationConfigStore()
push_notification_sender = BasePushNotificationSender(
client, config_store=push_notification_config_store
)
allowed_tickers = {"AAPL", "GOOGL", "MSFT", "NVDA", "TSLA"}


class HedgeFundRequest(BaseModel):
tickers: List[str] = Field(
...,
description=f"List of stock tickers to analyze. Must be from: {allowed_tickers}",
)
selected_analysts: List[str] = Field(
default=[],
description=f"List of analysts to use for analysis. If empty, all analysts will be used. Must be from {allowed_analysts}",
)

request_handler = DefaultRequestHandler(
agent_executor=VanillaAgentExecutor(),
task_store=InMemoryTaskStore(),
push_config_store=push_notification_config_store,
push_sender=push_notification_sender,
@field_validator("tickers")
@classmethod
def validate_tickers(cls, v):
invalid_tickers = set(v) - allowed_tickers
if invalid_tickers:
raise ValueError(
f"Invalid tickers: {invalid_tickers}. Allowed: {allowed_tickers}"
)
return v

@field_validator("selected_analysts")
@classmethod
def validate_analysts(cls, v):
if v: # Only validate if not empty
invalid_analysts = set(v) - allowed_analysts
if invalid_analysts:
raise ValueError(
f"Invalid analysts: {invalid_analysts}. Allowed: {allowed_analysts}"
)
return v


class AIHedgeFundAgent(BaseAgent):
def __init__(self):
super().__init__()
self.agno_agent = Agent(
model=OpenRouter(id="openai/gpt-4o-mini"),
response_model=HedgeFundRequest,
markdown=True,
)

server = A2AStarletteApplication(
agent_card=agent_card, http_handler=request_handler
async def stream(self, query, session_id, task_id):
run_response = self.agno_agent.run(
f"Parse the following hedge fund analysis request and extract the parameters: {query}"
)
hedge_fund_request = run_response.content

logger.info(f'Starting server on {host}:{port}')
end_date = datetime.now().strftime("%Y-%m-%d")
end_date_obj = datetime.strptime(end_date, "%Y-%m-%d")
start_date = (end_date_obj - relativedelta(months=3)).strftime("%Y-%m-%d")

initial_cash = 10000.00
portfolio = {
"cash": initial_cash,
"margin_requirement": 0,
"margin_used": 0.0,
"positions": {
ticker: {
"long": 0,
"short": 0,
"long_cost_basis": 0.0,
"short_cost_basis": 0.0,
"short_margin_used": 0.0,
}
for ticker in hedge_fund_request.tickers
},
"realized_gains": {
ticker: {
"long": 0.0,
"short": 0.0,
}
for ticker in hedge_fund_request.tickers
},
}

result = run_hedge_fund(
tickers=hedge_fund_request.tickers,
start_date=start_date,
end_date=end_date,
portfolio=portfolio,
model_name="openai/gpt-4o-mini",
model_provider="OpenRouter",
selected_analysts=hedge_fund_request.selected_analysts,
)

uvicorn.run(server.build(), host=host, port=port)
except FileNotFoundError:
logger.error(f"Error: File '{agent_card_file}' not found.")
sys.exit(1)
except json.JSONDecodeError:
logger.error(f"Error: File '{agent_card_file}' contains invalid JSON.")
sys.exit(1)
except Exception as e:
logger.error(f'An error occurred during server startup: {e}')
sys.exit(1)
yield {
"content": json.dumps(result),
"is_task_complete": True,
}


if __name__ == '__main__':
main()
if __name__ == "__main__":
agent = create_wrapped_agent(AIHedgeFundAgent)
asyncio.run(agent.serve())
28 changes: 0 additions & 28 deletions python/third_party/ai-hedge-fund/adapter/agent_executor.py

This file was deleted.

27 changes: 0 additions & 27 deletions python/third_party/ai-hedge-fund/adapter/hedge_fund_agent.json

This file was deleted.

111 changes: 0 additions & 111 deletions python/third_party/ai-hedge-fund/adapter/test_client.py

This file was deleted.

6 changes: 6 additions & 0 deletions python/third_party/ai-hedge-fund/launch_adapter.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
echo "Starting AI Hedge Fund Agent..."
echo "Python Environment Overview:"
echo "uv: $(which uv)"
echo "python: $(which python)"

python -m adapter
Loading