From 6fb4e2fe0389505f1d2f0570150318b2d058902d Mon Sep 17 00:00:00 2001 From: Stanislav Trifan Date: Thu, 7 May 2026 20:02:05 +0100 Subject: [PATCH] Create trading portfolio swarm --- .env.example | 45 +++- README.md | 194 +++++------------- market_research_agent/__init__.py | 3 + market_research_agent/instructions.md | 41 ++++ .../market_research_agent.py | 38 ++++ market_research_agent/tools/__init__.py | 1 + orchestrator/instructions.md | 51 +++-- orchestrator/orchestrator.py | 14 +- portfolio_manager_agent/__init__.py | 3 + portfolio_manager_agent/instructions.md | 52 +++++ .../portfolio_manager_agent.py | 36 ++++ portfolio_manager_agent/tools/__init__.py | 1 + quantitative_analyst_agent/__init__.py | 3 + quantitative_analyst_agent/instructions.md | 41 ++++ .../quantitative_analyst_agent.py | 36 ++++ quantitative_analyst_agent/tools/__init__.py | 1 + requirements.txt | 4 + risk_manager_agent/__init__.py | 3 + risk_manager_agent/instructions.md | 40 ++++ risk_manager_agent/risk_manager_agent.py | 36 ++++ risk_manager_agent/tools/__init__.py | 1 + shared_instructions.md | 18 +- swarm.py | 34 +-- trade_execution_agent/__init__.py | 3 + trade_execution_agent/instructions.md | 38 ++++ trade_execution_agent/tools/__init__.py | 1 + .../trade_execution_agent.py | 37 ++++ trading_tools/__init__.py | 13 ++ trading_tools/capability_detector.py | 81 ++++++++ trading_tools/common.py | 72 +++++++ trading_tools/ibkr_account_snapshot.py | 122 +++++++++++ trading_tools/ibkr_order_tool.py | 136 ++++++++++++ trading_tools/market_data_snapshot.py | 64 ++++++ trading_tools/quant_backtest.py | 99 +++++++++ 34 files changed, 1165 insertions(+), 197 deletions(-) create mode 100644 market_research_agent/__init__.py create mode 100644 market_research_agent/instructions.md create mode 100644 market_research_agent/market_research_agent.py create mode 100644 market_research_agent/tools/__init__.py create mode 100644 portfolio_manager_agent/__init__.py create mode 100644 portfolio_manager_agent/instructions.md create mode 100644 portfolio_manager_agent/portfolio_manager_agent.py create mode 100644 portfolio_manager_agent/tools/__init__.py create mode 100644 quantitative_analyst_agent/__init__.py create mode 100644 quantitative_analyst_agent/instructions.md create mode 100644 quantitative_analyst_agent/quantitative_analyst_agent.py create mode 100644 quantitative_analyst_agent/tools/__init__.py create mode 100644 risk_manager_agent/__init__.py create mode 100644 risk_manager_agent/instructions.md create mode 100644 risk_manager_agent/risk_manager_agent.py create mode 100644 risk_manager_agent/tools/__init__.py create mode 100644 trade_execution_agent/__init__.py create mode 100644 trade_execution_agent/instructions.md create mode 100644 trade_execution_agent/tools/__init__.py create mode 100644 trade_execution_agent/trade_execution_agent.py create mode 100644 trading_tools/__init__.py create mode 100644 trading_tools/capability_detector.py create mode 100644 trading_tools/common.py create mode 100644 trading_tools/ibkr_account_snapshot.py create mode 100644 trading_tools/ibkr_order_tool.py create mode 100644 trading_tools/market_data_snapshot.py create mode 100644 trading_tools/quant_backtest.py diff --git a/.env.example b/.env.example index 3c2edae0..d2429c1f 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ # ───────────────────────────────────────────── -# OpenSwarm — environment configuration +# Trading Portfolio Swarm — environment configuration # Copy this file to .env and fill in your keys. # Run `python run.py` to launch the onboarding # wizard, which fills this file automatically. @@ -29,8 +29,7 @@ DEFAULT_MODEL= # ── Optional: Composio ─────────────────────── -# Composio unlocks 10,000+ external integrations (Gmail, Slack, Google Calendar, -# HubSpot, GitHub, and more) for the General Agent. +# Composio unlocks external integrations when you add custom tools that need them. # Get your key and user ID at https://composio.dev COMPOSIO_API_KEY= COMPOSIO_USER_ID= @@ -44,27 +43,51 @@ COMPOSIO_USER_ID= SEARCH_API_KEY= +# ── Trading / IBKR ─────────────────────────── + +# Interactive Brokers TWS or IB Gateway API settings. +# TWS paper trading usually uses port 7497; live TWS usually uses 7496. +# IB Gateway paper trading usually uses 4002; live gateway usually uses 4001. +IBKR_HOST=127.0.0.1 +IBKR_PORT=7497 +IBKR_CLIENT_ID=19 +IBKR_ACCOUNT= +IBKR_READONLY=true + +# Live trading is disabled unless this is explicitly set to true. +# IBKR_TRADE_CONFIRMATION_TOKEN must match the token provided to the order tool. +IBKR_ENABLE_LIVE_TRADING=false +IBKR_TRADE_CONFIRMATION_TOKEN= + + +# ── Optional: Paid market data / research APIs ───────── + +POLYGON_API_KEY= +ALPHAVANTAGE_API_KEY= +FINNHUB_API_KEY= +FMP_API_KEY= +TIINGO_API_KEY= +NEWSAPI_API_KEY= + + # ── Optional: Images / Video ───────────────────────── -# Google AI / Gemini — used for image generation, editing, and composition -# (Gemini 2.5 Flash Image, Gemini 3 Pro Image) across Image Agent, Slides Agent, -# and Video Agent; also required for Veo video generation (Video Agent). +# Google AI / Gemini — reserved for custom extensions that need Gemini models. # Get your key at https://aistudio.google.com/app/apikey GOOGLE_API_KEY= -# Pexels — stock photo search used by Slides Agent (ImageSearch tool). +# Pexels — reserved for custom extensions that need stock image search. # Get your key at https://www.pexels.com/api PEXELS_API_KEY= -# Pixabay — stock photo search used by Slides Agent (ImageSearch tool). +# Pixabay — reserved for custom extensions that need stock image search. # Get your key at https://pixabay.com/api/docs PIXABAY_API_KEY= -# Unsplash — stock photo search used by Slides Agent (ImageSearch tool). +# Unsplash — reserved for custom extensions that need stock image search. # Get your key at https://unsplash.com/developers UNSPLASH_ACCESS_KEY= -# fal.ai — used for Seedance 1.5 Pro video generation, video editing (Video Agent), -# and background removal (Image Agent). +# fal.ai — reserved for custom extensions that need media generation. # Get your key at https://fal.ai/dashboard/keys FAL_KEY= diff --git a/README.md b/README.md index 0b063614..a9fe749a 100644 --- a/README.md +++ b/README.md @@ -1,162 +1,82 @@ -
+# Trading Portfolio Swarm -# 🚀 OpenSwarm +This fork of OpenSwarm is configured as a multi-agent trading and portfolio-management team. -![OpenSwarm](assets/new-framework.jpg) +It coordinates specialists for: -
+- IBKR API capability detection, account snapshots, market-data entitlement probes, order previews, and gated live order routing. +- Portfolio construction, rebalancing, and ROI-oriented allocation across daily, short, medium, and long horizons. +- Quantitative research, factor analysis, backtesting, and algorithm diagnostics. +- Market, sentiment, politics, macro, regulatory, and paid-research synthesis. +- Risk review, drawdown controls, liquidity checks, leverage limits, and pre-trade verdicts. -**The fully open-source multi-agent system that does everything Claude Code can't.** +Built on [Agency Swarm](https://github.com/VRSEN/agency-swarm). -Create polished slide decks, research reports, data visualizations, documents, images, and videos — all from a single prompt in your terminal. No platform, no UI, no setup hassles. +## Agent Roster -✨ **One prompt → Complete deliverables** -🎯 **8 specialized agents working together** -⚡ **Install in 30 seconds, running in 60** -🔧 **100% customizable and forkable** +| Agent | Owns | +|---|---| +| **Orchestrator** | Routes work to the right trading specialists. | +| **Trade Execution Agent** | IBKR connection checks, account snapshots, market-data probes, order previews, and safety-gated live orders. | +| **Portfolio Manager** | Allocation, position sizing, rebalancing, and multi-horizon portfolio plans. | +| **Quantitative Analyst** | Backtests, signals, factor analysis, and algorithm research. | +| **Market Research Analyst** | Fundamentals, news, sentiment, politics, macro, regulation, and research-source synthesis. | +| **Risk Manager** | Exposure, drawdown, liquidity, leverage, stops, margin, and pre-trade risk controls. | +| **Data Analyst** | General CSV/Excel analysis, dashboards, charts, KPIs, and broader statistics. | +| **Docs Agent** | Investment memos, research reports, trading journals, policies, SOPs, and formatted exports. | -Built on [Agency Swarm](https://github.com/VRSEN/agency-swarm) — the framework powering real AI agencies. - ---- - -> 💼 **Investor or looking to integrate AI agents into your SaaS?** -> We're the team behind OpenSwarm and Agency Swarm, building the future of multi-agent systems. -> **[Partner with us →](https://vrsen-ai.notion.site/fee2d391a8d74b24baa04a0b648af83c?pvs=105)** - ---- - -## 💡 What Makes This Different? - -Instead of one agent trying to do everything poorly, you get **specialists coordinated by an orchestrator**. - -### 🎯 Real Examples - -Paste these into your terminal and watch magic happen: - -- **"Create a complete investor pitch for OpenSwarm"** → Full deck + executive summary + market research -- **"Research my top 5 competitors and write 3 SEO-optimized blog posts"** → Competitive analysis + keyword research + publish-ready content -- **"Analyze this data and create a quarterly report with charts"** → Data insights + visualizations + formatted document -- **"Generate a product launch video with animations"** → Professional video with graphics and transitions -- **"Build me a marketing campaign for Q2"** → Strategy doc + creative assets + implementation timeline - -Connect to 10,000+ external services (Gmail, Slack, GitHub, HubSpot) via Composio for even more power. - ---- - -## 🤖 Meet Your AI Team - -| Agent | What it does | -| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Orchestrator** | Routes every user request to the right specialist(s). Never answers directly — pure coordination. | -| **Virtual Assistant** | Handles everyday tasks: writing, scheduling, messaging, task management. Gains 10,000+ external integrations via [Composio](https://composio.dev) (Gmail, Slack, GitHub, HubSpot, and more). | -| **Deep Research** | Conducts comprehensive, evidence-based web research with citations and balanced analysis. | -| **Data Analyst** | Analyses structured data, builds charts, runs statistical models — all inside an isolated IPython kernel. | -| **Slides Agent** | Generates complete, visually polished HTML slide decks, then exports them to PPTX. | -| **Docs Agent** | Creates formatted Word documents and PDFs from outlines or raw content. | -| **Image Generation Agent** | Generates and edits images using Gemini 2.5 Flash Image / Gemini 3 Pro Image and fal.ai. | -| **Video Generation Agent** | Produces videos via Sora (OpenAI), Veo (Google), and Seedance (fal.ai); also edits and combines clips. | - ---- - -## 📦 Get Started in 30 Seconds - -**For most users (recommended):** +## Setup ```bash -npm install -g @vrsen/openswarm -openswarm +cp .env.example .env +python3 -m venv .venv +. .venv/bin/activate +pip install -r requirements.txt +python3 run.py ``` -That's it! The setup wizard handles everything: authentication, dependencies, and configuration. - -**Requirements:** Node.js 20+ (Python 3.10+ auto-installed) - -## 🔧 Build Your Own Swarm +At minimum, configure one model provider key in `.env`, such as `OPENAI_API_KEY`. -Fork this repo and create your own specialized AI team in minutes: +For IBKR workflows, start TWS or IB Gateway, enable API access, and configure: -```bash -git clone https://github.com/VRSEN/openswarm.git -cd openswarm +```env +IBKR_HOST=127.0.0.1 +IBKR_PORT=7497 +IBKR_CLIENT_ID=19 +IBKR_ACCOUNT= +IBKR_READONLY=true ``` -Then tell **Claude Code**, **Cursor**, or **Codex**: - -> _"Turn this into an SEO optimization swarm"_ - -They'll automatically customize all agents for your use case. - -**Popular custom swarms:** - -- **SEO Swarm:** Keyword research + competitor analysis + blog writing -- **Sales Swarm:** Lead research + outreach + proposal generation -- **Marketing Swarm:** Campaign planning + creative assets + analytics -- **Product Swarm:** Market research + feature specs + launch materials +Live order submission is blocked unless both are configured: -## ⚙️ API Keys & Setup - -The setup wizard walks you through everything, but you'll need at least one of these: - -**Required (choose one):** - -- `OPENAI_API_KEY` - For GPT 5.5 and Sora video generation -- `ANTHROPIC_API_KEY` - For Claude models - -**Optional superpowers:** - -- `COMPOSIO_API_KEY` - Unlock 10,000+ integrations (Gmail, Slack, GitHub, etc.) -- `GOOGLE_API_KEY` - Gemini image generation + Veo video -- `FAL_KEY` - Advanced video editing and effects -- `SEARCH_API_KEY` - Web search for research agent - -Tools gracefully degrade when keys are missing — you'll get clear instructions on what to add. - ---- - -## 🚀 Coming Soon - -- **Agent Builder Agent** - Create custom swarms from a single prompt -- **OpenClaw + Claude Code integration** - All agents in one place - -⭐ **Star us on GitHub** to stay updated and help us prioritize features! - -## 🏗️ For Developers - -**Local development:** - -```bash -git clone https://github.com/VRSEN/openswarm.git -cd openswarm -python swarm.py +```env +IBKR_ENABLE_LIVE_TRADING=true +IBKR_TRADE_CONFIRMATION_TOKEN= ``` -**Docker deployment:** - -```bash -git clone https://github.com/VRSEN/openswarm.git -cd openswarm -cp .env.example .env # Add your API keys -docker-compose up --build -``` +## Optional Data Sources -**API server:** +The swarm detects available providers at runtime without exposing secret values: -```bash -python server.py # Runs on localhost:8080 +```env +POLYGON_API_KEY= +ALPHAVANTAGE_API_KEY= +FINNHUB_API_KEY= +FMP_API_KEY= +TIINGO_API_KEY= +NEWSAPI_API_KEY= +SEARCH_API_KEY= ``` ---- - -## 📺 Learn More - -- **Watch the full demo:** [YouTube video →](https://youtu.be/c5DdXzqaeVU?si=rM2CNaZ8qVwMvqmz) -- **Multi-agent framework:** [Agency Swarm](https://github.com/VRSEN/agency-swarm) -- **External integrations:** [Composio](https://composio.dev) - ---- +## Typical Prompts -## 📄 License +- "Check which IBKR and market-data capabilities are currently available." +- "Review my IBKR portfolio and propose risk-adjusted rebalancing actions." +- "Research AAPL using fundamentals, sentiment, politics/regulation, and market context." +- "Backtest a daily momentum strategy on SPY and compare it to buy-and-hold." +- "Risk-check this proposed QQQ trade and preview the IBKR order." +- "Analyze this trading log CSV and turn the findings into an investment memo." -MIT — see [LICENSE](LICENSE). +## Risk Notice -**Built with ❤️ by the team behind [Agency Swarm](https://github.com/VRSEN/agency-swarm)** +This software is a local automation framework. It does not guarantee returns and does not remove trading risk. Use paper trading first, verify all data and orders manually, and comply with your broker, tax, and regulatory obligations. diff --git a/market_research_agent/__init__.py b/market_research_agent/__init__.py new file mode 100644 index 00000000..430e6517 --- /dev/null +++ b/market_research_agent/__init__.py @@ -0,0 +1,3 @@ +from .market_research_agent import create_market_research_agent + +__all__ = ["create_market_research_agent"] diff --git a/market_research_agent/instructions.md b/market_research_agent/instructions.md new file mode 100644 index 00000000..e24af547 --- /dev/null +++ b/market_research_agent/instructions.md @@ -0,0 +1,41 @@ +# Role + +You are the **Market Research Analyst** for the trading swarm. + +# Scope + +You own: +- Company, sector, macro, political, geopolitical, regulatory, and sentiment research. +- Catalyst calendars, earnings context, analyst/research notes when available, and news synthesis. +- Interpreting emotional/behavioral market factors: positioning, crowd sentiment, narrative intensity, panic/euphoria, and event risk. +- Detecting paid research and news APIs available at runtime and using the best available source. + +# Research Rules + +- Use `TradingCapabilityDetector` before relying on paid research/data feeds. +- Use web research for current market, political, regulatory, and news claims. +- Prefer primary sources: filings, company releases, regulator/government publications, exchange notices, and central-bank sources. +- Cite sources for claims that are not derived from user-provided data. +- Distinguish fact, market narrative, consensus expectation, and your inference. +- If a paid API is detected but no direct tool exists, explain the required integration clearly instead of pretending to have queried it. + +# Output + +Use: + +**Market Setup** +- Current state, catalysts, and relevant macro/political context. + +**Sentiment and Narrative** +- Bullish and bearish narratives, emotional extremes, positioning clues, and confidence. + +**Fundamental Context** +- Valuation, growth, balance-sheet, competitive, or sector drivers if relevant. + +**Trade Implications** +- How the research supports or weakens daily, short, medium, and long-horizon ideas. + +**Sources and Limits** +- Inline source links and explicit gaps. + +Do not make execution decisions alone. Route portfolio sizing to Portfolio Manager and order placement to Trade Execution Agent. diff --git a/market_research_agent/market_research_agent.py b/market_research_agent/market_research_agent.py new file mode 100644 index 00000000..3042354f --- /dev/null +++ b/market_research_agent/market_research_agent.py @@ -0,0 +1,38 @@ +import os + +from agency_swarm import Agent, ModelSettings +from agency_swarm.tools import IPythonInterpreter, WebSearchTool +from openai.types.shared import Reasoning + +from config import get_default_model, is_openai_provider +from trading_tools import MarketDataSnapshot, TradingCapabilityDetector +from virtual_assistant.tools.ScholarSearch import ScholarSearch + +current_dir = os.path.dirname(os.path.abspath(__file__)) + + +def create_market_research_agent() -> Agent: + return Agent( + name="Market Research Analyst", + description="Market, fundamental, sentiment, politics, macro, and paid-research synthesis specialist.", + instructions=os.path.join(current_dir, "instructions.md"), + tools=[ + WebSearchTool(), + ScholarSearch, + TradingCapabilityDetector, + MarketDataSnapshot, + IPythonInterpreter, + ], + model=get_default_model(), + model_settings=ModelSettings( + reasoning=Reasoning(effort="high", summary="auto") if is_openai_provider() else None, + truncation="auto", + response_include=["web_search_call.action.sources"] if is_openai_provider() else None, + ), + conversation_starters=[ + "Research the market setup for semiconductors this week.", + "Analyze sentiment and political risk around a stock.", + "Compare analyst, news, and macro factors for my watchlist.", + "Detect which paid research APIs are available and use them if possible.", + ], + ) diff --git a/market_research_agent/tools/__init__.py b/market_research_agent/tools/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/market_research_agent/tools/__init__.py @@ -0,0 +1 @@ + diff --git a/orchestrator/instructions.md b/orchestrator/instructions.md index 1112adb6..5e0cd00c 100644 --- a/orchestrator/instructions.md +++ b/orchestrator/instructions.md @@ -1,14 +1,14 @@ # Role -You are an Agent Swarm and you act as an **orchestrator**, the main entrypoint for this agency. +You are an Agent Swarm and you act as an **orchestrator**, the main entrypoint for this trading and portfolio agency. Your **only** job is to turn user goals into the right multi-agent execution strategy and **route** work to specialists. You do not execute any task yourself. # Routing Only (Critical) You must **never** handle tasks yourself. Do not: -- Research, write content, or analyze data. -- Create or edit slides, documents, images, or video. +- Research securities, write market theses, or analyze data. +- Build portfolios, size positions, risk-check trades, or place orders. - Answer substantive questions that belong to a specialist. - Synthesize or generate deliverables—specialists do that. @@ -28,8 +28,8 @@ Use exactly one of these patterns per subtask: Use `SendMessage` when specialist subtasks are independent and can run in parallel. Examples: -- Run research and data analysis simultaneously. -- Generate document and visual assets independently. +- Run market research, quantitative analysis, risk analysis, and execution checks simultaneously. +- Ask independent specialists to evaluate the same trade idea from different perspectives. In this mode, you gather outputs from specialists and synthesize a unified final response. Never use `SendMessage` for a single-specialist task, even to fetch clarifying questions or “keep control of the chat.” Clarifying questions must be asked by the specialist after Handoff. @@ -48,10 +48,10 @@ Specialists own file delivery end-to-end. Use `Handoff` whenever a task can be handled by a **single specialist agent** — this is the default for any single-agent task. The specialist gets the full conversation history and can iterate directly with the user without you in the loop. Examples: -- Any task owned end-to-end by one specialist (slides, docs, research, video, image, data). -- Detailed slide polishing with multiple user revision rounds. -- Deep document editing with line-by-line user feedback. -- Video refinement where user repeatedly approves/adjusts outputs. +- Any task owned end-to-end by one trading specialist. +- Iterative portfolio review with the Portfolio Manager. +- IBKR setup, account inspection, and order preview with the Trade Execution Agent. +- Standalone market research with the Market Research Analyst. **Rule: if only one specialist is needed, always use `Handoff`.** Use `SendMessage` only when two or more specialist subtasks must run in parallel. @@ -59,13 +59,32 @@ In this mode, transfer control early to the best specialist. # Routing Guide -- **General Agent**: administrative workflows, external systems, messaging, scheduling. -- **Deep Research Agent**: evidence-based research and source-backed analysis. -- **Data Analyst**: data analysis, KPIs, charts, and analytical insights. -- **Slides Agent**: presentation creation, editing, and exports. -- **Docs Agent**: document creation, editing, and conversion. -- **Video Agent**: video generation/editing/assembly. -- **Image Agent**: image generation/editing/composition. +- **Trade Execution Agent**: IBKR capability detection, account snapshots, market-data entitlement checks, order previews, and gated live orders. +- **Portfolio Manager**: portfolio construction, allocation, rebalancing, and ROI-oriented trade prioritization. +- **Quantitative Analyst**: backtesting, factor analysis, signal design, and algorithm diagnostics. +- **Market Research Analyst**: fundamentals, news, sentiment, macro, politics, regulation, and paid research source synthesis. +- **Risk Manager**: pre-trade risk checks, exposure limits, drawdown controls, liquidity, leverage, and monitoring rules. +- **Data Analyst**: general-purpose data analysis, CSV/Excel analysis, dashboards, charts, KPI summaries, and non-trading-specific data work. +- **Docs Agent**: investment memos, research reports, trading journals, policy documents, SOPs, and formatted deliverables. + +# Trading-Specific Routing + +- For "what is available", "connect to IBKR", "positions", "orders", "quotes", or "subscriptions", route to Trade Execution Agent. +- For "what should I buy/sell/hold", "rebalance", "portfolio ROI", or "allocation", use Portfolio Manager and usually gather parallel inputs from Quantitative Analyst, Market Research Analyst, Risk Manager, and Trade Execution Agent. +- For "backtest", "algorithm", "signal", "factor", "statistics", or "optimize", route to Quantitative Analyst. +- For "news", "sentiment", "politics", "macro", "earnings", "research APIs", or "market analysis", route to Market Research Analyst. +- For "risk", "drawdown", "position size", "stop", "leverage", "margin", or "pre-trade approval", route to Risk Manager. +- For actual live order submission, route only to Trade Execution Agent after Risk Manager and Portfolio Manager have weighed in unless the user explicitly asks only for an execution-tool operation. +- For uploaded CSV/Excel files, dashboards, or broad data exploration that is not specifically a trading signal/backtest, route to Data Analyst. If the data analysis supports a trade decision, combine it with Portfolio Manager and Risk Manager. +- For investment memos, market briefs, strategy policies, trading journals, reports, or formatted documentation, route to Docs Agent after the relevant specialists provide content. + +# Recommended Multi-Agent Flows + +- **Trade idea evaluation**: Market Research Analyst + Quantitative Analyst + Risk Manager in parallel, then Portfolio Manager synthesizes allocation, then Trade Execution Agent previews any order. +- **Portfolio review**: Trade Execution Agent snapshots account + Risk Manager checks exposures + Quantitative Analyst analyzes performance, then Portfolio Manager synthesizes actions. +- **Daily trading plan**: Market Research Analyst for catalysts/sentiment + Quantitative Analyst for signals + Risk Manager for limits, then Portfolio Manager ranks opportunities. +- **Research report or memo**: Run relevant trading specialists first, then send their outputs to Docs Agent for formatting and final delivery. +- **Uploaded data analysis**: Data Analyst handles cleaning/charts/statistics; route to Quantitative Analyst only when the user needs strategy signals or backtests. # Workflow diff --git a/orchestrator/orchestrator.py b/orchestrator/orchestrator.py index ef3ceb6f..c4412d86 100644 --- a/orchestrator/orchestrator.py +++ b/orchestrator/orchestrator.py @@ -11,8 +11,8 @@ def create_orchestrator() -> Agent: return Agent( name="Orchestrator", description=( - "Primary coordinator that plans multi-agent workflows, runs independent workstreams in parallel, " - "and hands off to a specialist when tight user iteration is needed." + "Primary coordinator for trading, portfolio management, market research, risk, quant analysis, " + "and IBKR execution workflows." ), instructions="./instructions.md", model=get_default_model(), @@ -20,14 +20,14 @@ def create_orchestrator() -> Agent: reasoning=Reasoning(effort="medium", summary="auto") if is_openai_provider() else None, ), conversation_starters=[ - "What can this agency do?", - "Build a full launch package: research, slides, docs, and creative assets.", - "Analyze my data and then turn insights into a polished executive deck.", - "Coordinate a workflow for proposal doc + promo visuals + short product video.", + "Check what trading APIs and market-data subscriptions are available.", + "Review my IBKR portfolio and suggest risk-adjusted trade priorities.", + "Research and backtest a trade idea before order preview.", + "Build daily, swing, medium-term, and long-term trading workflows.", ], ) if __name__ == "__main__": from agency_swarm import Agency - Agency(create_orchestrator()).terminal_demo() \ No newline at end of file + Agency(create_orchestrator()).terminal_demo() diff --git a/portfolio_manager_agent/__init__.py b/portfolio_manager_agent/__init__.py new file mode 100644 index 00000000..0c88baab --- /dev/null +++ b/portfolio_manager_agent/__init__.py @@ -0,0 +1,3 @@ +from .portfolio_manager_agent import create_portfolio_manager_agent + +__all__ = ["create_portfolio_manager_agent"] diff --git a/portfolio_manager_agent/instructions.md b/portfolio_manager_agent/instructions.md new file mode 100644 index 00000000..7f2169db --- /dev/null +++ b/portfolio_manager_agent/instructions.md @@ -0,0 +1,52 @@ +# Role + +You are the **Portfolio Manager** for a trading and stock-portfolio swarm. + +# Scope + +You own: +- Portfolio construction, position sizing, allocation, rebalancing, and capital deployment. +- Strategy selection across daily, short-term, medium-term, long-term, and algorithmic horizons. +- ROI-oriented prioritization after accounting for risk, liquidity, tax/friction assumptions, and opportunity cost. +- Converting specialist inputs into a coherent portfolio action plan. + +# Required Inputs + +Use live or uploaded portfolio data whenever possible. For IBKR portfolios, call `IBKRAccountSnapshot`. If account access is unavailable, state what is missing and use only explicitly provided holdings. + +When the task involves a recommendation, seek or require: +- Quantitative evidence from the Quantitative Analyst. +- Market/fundamental/sentiment/political context from the Market Research Analyst. +- Drawdown, exposure, and pre-trade constraints from the Risk Manager. +- Execution feasibility from the Trade Execution Agent. + +# Decision Framework + +Assess every portfolio action by: +- Expected return and time horizon. +- Probability-weighted downside. +- Correlation with existing holdings. +- Liquidity, spread, borrow availability when relevant, and execution friction. +- Catalyst timing and regime sensitivity. +- Portfolio-level concentration, leverage, and cash impact. + +# Output + +For portfolio plans, use: + +**Portfolio State** +- Holdings, cash, exposures, and gaps known from data. + +**Opportunity Ranking** +- Best candidates by horizon and expected ROI after risk adjustment. + +**Allocation Plan** +- Position sizing, entry zone, exit/stop logic, and review cadence. + +**Risks** +- Key ways the plan can fail and what would invalidate it. + +**Execution Notes** +- What the Trade Execution Agent should verify or preview. + +Do not claim guaranteed returns. Distinguish data-backed conclusions from assumptions. diff --git a/portfolio_manager_agent/portfolio_manager_agent.py b/portfolio_manager_agent/portfolio_manager_agent.py new file mode 100644 index 00000000..41ce4501 --- /dev/null +++ b/portfolio_manager_agent/portfolio_manager_agent.py @@ -0,0 +1,36 @@ +import os + +from agency_swarm import Agent, ModelSettings +from agency_swarm.tools import IPythonInterpreter, PersistentShellTool +from openai.types.shared import Reasoning + +from config import get_default_model, is_openai_provider +from trading_tools import IBKRAccountSnapshot, MarketDataSnapshot, TradingCapabilityDetector + +current_dir = os.path.dirname(os.path.abspath(__file__)) + + +def create_portfolio_manager_agent() -> Agent: + return Agent( + name="Portfolio Manager", + description="Portfolio construction and allocation specialist for daily, short, medium, and long-horizon strategies.", + instructions=os.path.join(current_dir, "instructions.md"), + tools=[ + TradingCapabilityDetector, + IBKRAccountSnapshot, + MarketDataSnapshot, + IPythonInterpreter, + PersistentShellTool, + ], + model=get_default_model(), + model_settings=ModelSettings( + reasoning=Reasoning(effort="high", summary="auto") if is_openai_provider() else None, + truncation="auto", + ), + conversation_starters=[ + "Review my portfolio allocation and suggest rebalancing priorities.", + "Design a multi-horizon equity portfolio process.", + "Rank trade candidates by expected ROI and risk.", + "Create daily, swing, and long-term portfolio rules.", + ], + ) diff --git a/portfolio_manager_agent/tools/__init__.py b/portfolio_manager_agent/tools/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/portfolio_manager_agent/tools/__init__.py @@ -0,0 +1 @@ + diff --git a/quantitative_analyst_agent/__init__.py b/quantitative_analyst_agent/__init__.py new file mode 100644 index 00000000..481beb19 --- /dev/null +++ b/quantitative_analyst_agent/__init__.py @@ -0,0 +1,3 @@ +from .quantitative_analyst_agent import create_quantitative_analyst_agent + +__all__ = ["create_quantitative_analyst_agent"] diff --git a/quantitative_analyst_agent/instructions.md b/quantitative_analyst_agent/instructions.md new file mode 100644 index 00000000..e69dc038 --- /dev/null +++ b/quantitative_analyst_agent/instructions.md @@ -0,0 +1,41 @@ +# Role + +You are the **Quantitative Analyst** for the trading swarm. + +# Scope + +You own: +- Quantitative signal research, factor analysis, backtesting, and statistical validation. +- Daily, swing, medium-term, and long-term strategy diagnostics. +- Feature engineering for price, volume, volatility, fundamentals, macro, sentiment, and event data. +- Algorithm design before handoff to execution or portfolio construction. + +# Workflow + +1. Detect available data capabilities with `TradingCapabilityDetector`. +2. Pull price data with the best available tool or use user-provided datasets. +3. Establish baseline: buy-and-hold, volatility, drawdown, turnover, and transaction-cost assumptions. +4. Test candidate signals out-of-sample when enough data exists. +5. Report metrics that matter: CAGR, Sharpe/Sortino when valid, max drawdown, hit rate, exposure, turnover, capacity, and slippage sensitivity. +6. Call out data snooping, look-ahead bias, survivorship bias, and insufficient sample sizes. + +# Output + +Use: + +**Data and Assumptions** +- Source, symbols, interval, period, transaction costs, and sample limits. + +**Signal Results** +- Metrics, benchmark comparison, and current signal state. + +**Robustness** +- Parameter sensitivity, failure regimes, and data-quality concerns. + +**Implementation Notes** +- Features, thresholds, rebalance cadence, and execution constraints. + +**Recommendation** +- Whether the signal is ready for paper trading, more research, or rejection. + +Never present a backtest as proof of future performance. diff --git a/quantitative_analyst_agent/quantitative_analyst_agent.py b/quantitative_analyst_agent/quantitative_analyst_agent.py new file mode 100644 index 00000000..c2cfc9f0 --- /dev/null +++ b/quantitative_analyst_agent/quantitative_analyst_agent.py @@ -0,0 +1,36 @@ +import os + +from agency_swarm import Agent, ModelSettings +from agency_swarm.tools import IPythonInterpreter, PersistentShellTool +from openai.types.shared import Reasoning + +from config import get_default_model, is_openai_provider +from trading_tools import MarketDataSnapshot, QuantBacktest, TradingCapabilityDetector + +current_dir = os.path.dirname(os.path.abspath(__file__)) + + +def create_quantitative_analyst_agent() -> Agent: + return Agent( + name="Quantitative Analyst", + description="Quantitative research specialist for factor analysis, backtesting, signals, and algorithm design.", + instructions=os.path.join(current_dir, "instructions.md"), + tools=[ + TradingCapabilityDetector, + MarketDataSnapshot, + QuantBacktest, + IPythonInterpreter, + PersistentShellTool, + ], + model=get_default_model(), + model_settings=ModelSettings( + reasoning=Reasoning(effort="high", summary="auto") if is_openai_provider() else None, + truncation="auto", + ), + conversation_starters=[ + "Backtest a moving-average strategy on SPY.", + "Build a factor screen for large-cap stocks.", + "Analyze volatility and drawdown for my watchlist.", + "Design an intraday algorithm research plan.", + ], + ) diff --git a/quantitative_analyst_agent/tools/__init__.py b/quantitative_analyst_agent/tools/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/quantitative_analyst_agent/tools/__init__.py @@ -0,0 +1 @@ + diff --git a/requirements.txt b/requirements.txt index 77db5c33..60dc3067 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,10 @@ numpy>=1.24.0 scipy>=1.10.0 statsmodels>=0.14.0 scikit-learn>=1.3.0 +ib-insync>=0.9.86 +yfinance>=0.2.50 +ta>=0.11.0 +vaderSentiment>=3.3.2 # Visualization matplotlib>=3.5.0 diff --git a/risk_manager_agent/__init__.py b/risk_manager_agent/__init__.py new file mode 100644 index 00000000..dec02176 --- /dev/null +++ b/risk_manager_agent/__init__.py @@ -0,0 +1,3 @@ +from .risk_manager_agent import create_risk_manager_agent + +__all__ = ["create_risk_manager_agent"] diff --git a/risk_manager_agent/instructions.md b/risk_manager_agent/instructions.md new file mode 100644 index 00000000..80176f3e --- /dev/null +++ b/risk_manager_agent/instructions.md @@ -0,0 +1,40 @@ +# Role + +You are the **Risk Manager** for the trading swarm. + +# Scope + +You own: +- Portfolio and trade-level risk controls. +- Pre-trade risk checks: concentration, liquidity, volatility, drawdown, correlation, leverage, and stop logic. +- Daily loss limits, kill switches, exposure caps, and scenario stress tests. +- Compliance-oriented warnings for pattern day trading, margin, restricted instruments, and unsuitable leverage. + +# Workflow + +1. Use live portfolio/account data when available via `IBKRAccountSnapshot`. +2. Estimate position risk using price/volatility data from available sources. +3. Stress-test proposed trades against adverse gaps, volatility expansion, correlation breakdown, and liquidity constraints. +4. Define hard limits before execution: max loss, stop level, invalidation condition, position size, and review cadence. +5. Escalate execution details to Trade Execution Agent and allocation choices to Portfolio Manager. + +# Output + +Use: + +**Risk Verdict** +- Approve, approve with limits, reject, or need more data. + +**Key Risk Drivers** +- Exposure, volatility, liquidity, correlation, leverage, and event risks. + +**Required Limits** +- Max position size, max loss, stop/invalidation, and portfolio caps. + +**Monitoring** +- Intraday/daily checks and triggers for reducing or exiting. + +**Assumptions** +- Data gaps and confidence level. + +Do not guarantee safety. A trade can satisfy risk rules and still lose money. diff --git a/risk_manager_agent/risk_manager_agent.py b/risk_manager_agent/risk_manager_agent.py new file mode 100644 index 00000000..52c3144e --- /dev/null +++ b/risk_manager_agent/risk_manager_agent.py @@ -0,0 +1,36 @@ +import os + +from agency_swarm import Agent, ModelSettings +from agency_swarm.tools import IPythonInterpreter, PersistentShellTool +from openai.types.shared import Reasoning + +from config import get_default_model, is_openai_provider +from trading_tools import IBKRAccountSnapshot, MarketDataSnapshot, TradingCapabilityDetector + +current_dir = os.path.dirname(os.path.abspath(__file__)) + + +def create_risk_manager_agent() -> Agent: + return Agent( + name="Risk Manager", + description="Trading risk specialist for exposure, drawdown, liquidity, compliance, and pre-trade checks.", + instructions=os.path.join(current_dir, "instructions.md"), + tools=[ + TradingCapabilityDetector, + IBKRAccountSnapshot, + MarketDataSnapshot, + IPythonInterpreter, + PersistentShellTool, + ], + model=get_default_model(), + model_settings=ModelSettings( + reasoning=Reasoning(effort="high", summary="auto") if is_openai_provider() else None, + truncation="auto", + ), + conversation_starters=[ + "Risk-check this proposed trade.", + "Analyze portfolio drawdown and concentration risk.", + "Create daily risk limits for my trading system.", + "Review leverage, stop-loss, and exposure constraints.", + ], + ) diff --git a/risk_manager_agent/tools/__init__.py b/risk_manager_agent/tools/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/risk_manager_agent/tools/__init__.py @@ -0,0 +1 @@ + diff --git a/shared_instructions.md b/shared_instructions.md index b62c1d8f..6be9832f 100644 --- a/shared_instructions.md +++ b/shared_instructions.md @@ -1,4 +1,4 @@ -# Shared Runtime Instructions (All Agents) +# Shared Runtime Instructions (All Trading Agents) You are a part of a multi-agent system built on the Agency Swarm framework. These instructions apply to every agent in this agency. @@ -99,13 +99,13 @@ You work as a part of the bigger agency that consist of following AI agents: | Agent name | Role | Owns | |---|---|---| | **Agent Swarm** | Orchestrator — entry point for all user requests | Routing only; never executes tasks | -| **General Agent** | Virtual assistant | External systems, messaging, scheduling, 10 000+ integrations via Composio | -| **Deep Research Agent** | Researcher | Evidence-based research and source-backed analysis. Access to scholar search | -| **Data Analyst** | Analyst | Data analysis, KPIs, charts creation, and analytical insights | -| **Slides Agent** | Presentation engineer | PowerPoint creation, editing, and `.pptx` export | -| **Docs Agent** | Document engineer | Document creation, editing, and conversion (PDF, DOCX, Markdown, TXT) | -| **Image Agent** | Image specialist | Image generation, editing, and composition | -| **Video Agent** | Video specialist | Video generation, editing, and assembly | +| **Trade Execution Agent** | IBKR execution specialist | Capability detection, account snapshots, market-data probes, order previews, and gated live order submission | +| **Portfolio Manager** | Portfolio construction specialist | Allocation, rebalancing, position sizing, ROI prioritization, and multi-horizon portfolio plans | +| **Quantitative Analyst** | Quant research specialist | Backtesting, factor analysis, statistical validation, signal design, and algorithm diagnostics | +| **Market Research Analyst** | Market research specialist | Fundamentals, news, sentiment, politics, macro, regulation, and paid research synthesis | +| **Risk Manager** | Risk specialist | Drawdown controls, concentration, liquidity, leverage, margin, stops, and pre-trade risk verdicts | +| **Data Analyst** | General data specialist | CSV/Excel analysis, dashboards, charts, KPI analysis, and general statistical exploration | +| **Docs Agent** | Document specialist | Investment memos, research reports, trading journals, policy docs, SOPs, and document export | ### 6.2 Communication topology @@ -116,7 +116,7 @@ Every agent can transfer to any other agent directly using its `transfer_to_` tool. 5. **Maintain project structure.** After a new specialist agent is selected **make sure** to keep using same `project_name` to keep a clean folder structure, unless user's request is not related to a previous project. diff --git a/swarm.py b/swarm.py index 7ddfde43..15af00bb 100644 --- a/swarm.py +++ b/swarm.py @@ -24,33 +24,33 @@ def create_agency(load_threads_callback=None): from agency_swarm import Agency from agency_swarm.tools import Handoff, SendMessage - from orchestrator import create_orchestrator - from virtual_assistant import create_virtual_assistant - from deep_research import create_deep_research from data_analyst_agent import create_data_analyst - from slides_agent import create_slides_agent from docs_agent import create_docs_agent - from video_generation_agent import create_video_generation_agent - from image_generation_agent import create_image_generation_agent + from orchestrator import create_orchestrator + from market_research_agent import create_market_research_agent + from portfolio_manager_agent import create_portfolio_manager_agent + from quantitative_analyst_agent import create_quantitative_analyst_agent + from risk_manager_agent import create_risk_manager_agent + from trade_execution_agent import create_trade_execution_agent orchestrator = create_orchestrator() - virtual_assistant = create_virtual_assistant() - deep_research = create_deep_research() + trade_execution_agent = create_trade_execution_agent() + portfolio_manager = create_portfolio_manager_agent() + quantitative_analyst = create_quantitative_analyst_agent() + market_research_analyst = create_market_research_agent() + risk_manager = create_risk_manager_agent() data_analyst = create_data_analyst() - slides_agent = create_slides_agent() docs_agent = create_docs_agent() - video_generation_agent = create_video_generation_agent() - image_generation_agent = create_image_generation_agent() all_agents = [ orchestrator, - virtual_assistant, - slides_agent, - deep_research, + trade_execution_agent, + portfolio_manager, + quantitative_analyst, + market_research_analyst, + risk_manager, data_analyst, docs_agent, - video_generation_agent, - image_generation_agent, ] send_message_flows = [ @@ -69,7 +69,7 @@ def create_agency(load_threads_callback=None): agency = Agency( *all_agents, communication_flows=send_message_flows + handoff_flows, - name="OpenSwarm", + name="Trading Portfolio Swarm", shared_instructions="shared_instructions.md", load_threads_callback=load_threads_callback, ) diff --git a/trade_execution_agent/__init__.py b/trade_execution_agent/__init__.py new file mode 100644 index 00000000..0028cbee --- /dev/null +++ b/trade_execution_agent/__init__.py @@ -0,0 +1,3 @@ +from .trade_execution_agent import create_trade_execution_agent + +__all__ = ["create_trade_execution_agent"] diff --git a/trade_execution_agent/instructions.md b/trade_execution_agent/instructions.md new file mode 100644 index 00000000..3547c20c --- /dev/null +++ b/trade_execution_agent/instructions.md @@ -0,0 +1,38 @@ +# Role + +You are the **Trade Execution Agent**, an Interactive Brokers specialist. + +# Scope + +You own: +- Runtime capability detection for IBKR, paid market-data subscriptions, and paid research/data APIs. +- IBKR account snapshots: accounts, balances, positions, open orders, and entitlement probes. +- Order construction, validation, and preview. +- Live order submission only when all configured safety gates are satisfied. + +# Mandatory Safety Rules + +- Start execution workflows with `TradingCapabilityDetector` unless the user has just supplied a fresh capability snapshot. +- Prefer previews. Do not submit live orders unless the user explicitly requests submission in the current conversation. +- Live submission requires `IBKR_ENABLE_LIVE_TRADING=true` and a matching `IBKR_TRADE_CONFIRMATION_TOKEN`; use `IBKROrderTool` exactly as designed. +- Never invent account balances, positions, fills, or quotes. Fetch them. +- Do not provide investment advice by yourself. For trade recommendations, require input from Portfolio Manager, Quantitative Analyst, Market Research Analyst, and Risk Manager as appropriate. +- If IBKR is unavailable, report exactly what is missing: package, host/port, TWS/Gateway API setting, credentials, account, or subscription. + +# Workflow + +1. Detect available capabilities. +2. Verify IBKR connection and account state. +3. For market data, probe representative symbols or requested symbols. +4. For orders, produce a preview first with symbol, action, quantity, order type, time in force, and account. +5. Before live submission, state the concrete order being submitted and require the configured confirmation token. +6. Report broker status and order id after submission. + +# Output + +Keep responses operational: +- Available connections and data sources. +- Account and position facts used. +- Order preview or submission result. +- Missing prerequisites. +- Any handoff needed for research, risk, portfolio, or quant analysis. diff --git a/trade_execution_agent/tools/__init__.py b/trade_execution_agent/tools/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/trade_execution_agent/tools/__init__.py @@ -0,0 +1 @@ + diff --git a/trade_execution_agent/trade_execution_agent.py b/trade_execution_agent/trade_execution_agent.py new file mode 100644 index 00000000..72757d0c --- /dev/null +++ b/trade_execution_agent/trade_execution_agent.py @@ -0,0 +1,37 @@ +import os + +from agency_swarm import Agent, ModelSettings +from agency_swarm.tools import IPythonInterpreter, PersistentShellTool +from openai.types.shared import Reasoning + +from config import get_default_model, is_openai_provider +from trading_tools import IBKRAccountSnapshot, IBKROrderTool, MarketDataSnapshot, TradingCapabilityDetector + +current_dir = os.path.dirname(os.path.abspath(__file__)) + + +def create_trade_execution_agent() -> Agent: + return Agent( + name="Trade Execution Agent", + description="IBKR execution specialist for account snapshots, market-data checks, order previews, and gated live order routing.", + instructions=os.path.join(current_dir, "instructions.md"), + tools=[ + TradingCapabilityDetector, + IBKRAccountSnapshot, + IBKROrderTool, + MarketDataSnapshot, + IPythonInterpreter, + PersistentShellTool, + ], + model=get_default_model(), + model_settings=ModelSettings( + reasoning=Reasoning(effort="high", summary="auto") if is_openai_provider() else None, + truncation="auto", + ), + conversation_starters=[ + "Check which IBKR and market-data capabilities are available.", + "Show my IBKR account snapshot and current positions.", + "Preview a risk-checked order for SPY.", + "Probe whether live data is available for AAPL and QQQ.", + ], + ) diff --git a/trading_tools/__init__.py b/trading_tools/__init__.py new file mode 100644 index 00000000..a047dc65 --- /dev/null +++ b/trading_tools/__init__.py @@ -0,0 +1,13 @@ +from .capability_detector import TradingCapabilityDetector +from .ibkr_account_snapshot import IBKRAccountSnapshot +from .ibkr_order_tool import IBKROrderTool +from .market_data_snapshot import MarketDataSnapshot +from .quant_backtest import QuantBacktest + +__all__ = [ + "TradingCapabilityDetector", + "IBKRAccountSnapshot", + "IBKROrderTool", + "MarketDataSnapshot", + "QuantBacktest", +] diff --git a/trading_tools/capability_detector.py b/trading_tools/capability_detector.py new file mode 100644 index 00000000..c05e402c --- /dev/null +++ b/trading_tools/capability_detector.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +import json +import os +from datetime import datetime, timezone + +from agency_swarm.tools import BaseTool +from pydantic import Field + +from .common import env_present, ibkr_settings, package_available, socket_open + + +class TradingCapabilityDetector(BaseTool): + """ + Detects currently available trading, market-data, research, and sentiment capabilities. + + Use this before any trading workflow to discover which paid APIs, data subscriptions, + Python packages, and IBKR gateway/TWS connections are actually available at runtime. + """ + + include_environment_names: bool = Field( + default=True, + description="If true, include the names of detected environment variables without exposing their secret values.", + ) + test_ibkr_socket: bool = Field( + default=True, + description="If true, test whether the configured IBKR host and port accept TCP connections.", + ) + + def run(self): + settings = ibkr_settings() + packages = { + "ib_insync": package_available("ib_insync"), + "yfinance": package_available("yfinance"), + "pandas": package_available("pandas"), + "numpy": package_available("numpy"), + "scipy": package_available("scipy"), + "statsmodels": package_available("statsmodels"), + "sklearn": package_available("sklearn"), + "ta": package_available("ta"), + "transformers": package_available("transformers"), + "vaderSentiment": package_available("vaderSentiment"), + } + providers = { + "IBKR": { + "configured": True, + "host": settings.host, + "port": settings.port, + "client_id": settings.client_id, + "account_configured": bool(settings.account), + "readonly_default": settings.readonly, + "ib_insync_installed": packages["ib_insync"], + "socket_reachable": socket_open(settings.host, settings.port) if self.test_ibkr_socket else None, + }, + "Polygon": {"api_key_present": env_present("POLYGON_API_KEY")}, + "Alpha Vantage": {"api_key_present": env_present("ALPHAVANTAGE_API_KEY")}, + "Finnhub": {"api_key_present": env_present("FINNHUB_API_KEY")}, + "FMP": {"api_key_present": env_present("FMP_API_KEY")}, + "Tiingo": {"api_key_present": env_present("TIINGO_API_KEY")}, + "NewsAPI": {"api_key_present": env_present("NEWSAPI_API_KEY")}, + "OpenAI": {"api_key_present": env_present("OPENAI_API_KEY")}, + } + research_env_names = [ + name + for name in os.environ + if any(token in name.upper() for token in ("RESEARCH", "NEWS", "SENTIMENT", "MARKETDATA", "DATA_API")) + ] + payload = { + "checked_at": datetime.now(timezone.utc).isoformat(), + "packages": packages, + "providers": providers, + "detected_research_environment_variables": sorted(research_env_names) + if self.include_environment_names + else [], + "next_steps": [ + "Start Trader Workstation or IB Gateway and enable API access if IBKR socket is not reachable.", + "Install missing packages from requirements.txt if a requested workflow depends on them.", + "Run IBKRAccountSnapshot to verify live account, entitlements, positions, and market data access.", + ], + } + return json.dumps(payload, indent=2, default=str) diff --git a/trading_tools/common.py b/trading_tools/common.py new file mode 100644 index 00000000..464078b4 --- /dev/null +++ b/trading_tools/common.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import importlib.util +import os +import socket +from contextlib import closing +from dataclasses import dataclass +from typing import Any + +from dotenv import load_dotenv + +load_dotenv() + + +def package_available(package_name: str) -> bool: + return importlib.util.find_spec(package_name) is not None + + +def env_present(name: str) -> bool: + value = os.getenv(name) + return bool(value and value.strip()) + + +def bool_env(name: str, default: bool = False) -> bool: + value = os.getenv(name) + if value is None: + return default + return value.strip().lower() in {"1", "true", "yes", "on"} + + +def int_env(name: str, default: int) -> int: + try: + return int(os.getenv(name, str(default))) + except ValueError: + return default + + +@dataclass(frozen=True) +class IBKRSettings: + host: str + port: int + client_id: int + account: str | None + readonly: bool + + +def ibkr_settings() -> IBKRSettings: + return IBKRSettings( + host=os.getenv("IBKR_HOST", "127.0.0.1"), + port=int_env("IBKR_PORT", 7497), + client_id=int_env("IBKR_CLIENT_ID", 19), + account=os.getenv("IBKR_ACCOUNT") or None, + readonly=bool_env("IBKR_READONLY", True), + ) + + +def socket_open(host: str, port: int, timeout: float = 2.0) -> bool: + try: + with closing(socket.create_connection((host, port), timeout=timeout)): + return True + except OSError: + return False + + +def compact_error(exc: Exception) -> dict[str, str]: + return {"type": exc.__class__.__name__, "message": str(exc)} + + +def dataframe_tail_json(df: Any, rows: int = 5) -> list[dict[str, Any]]: + if df is None or getattr(df, "empty", True): + return [] + return df.tail(rows).reset_index().to_dict(orient="records") diff --git a/trading_tools/ibkr_account_snapshot.py b/trading_tools/ibkr_account_snapshot.py new file mode 100644 index 00000000..b9b3b233 --- /dev/null +++ b/trading_tools/ibkr_account_snapshot.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +import json +from datetime import datetime, timezone + +from agency_swarm.tools import BaseTool +from pydantic import Field + +from .common import compact_error, ibkr_settings, package_available + + +class IBKRAccountSnapshot(BaseTool): + """ + Connects to Interactive Brokers TWS/IB Gateway and returns accounts, positions, + balances, open orders, and optional market-data entitlement probes. + """ + + include_positions: bool = Field(default=True, description="Include current positions.") + include_open_orders: bool = Field(default=True, description="Include open orders.") + market_data_probe_symbols: list[str] = Field( + default=[], + description="Optional stock symbols to probe with IBKR market data, e.g. ['SPY', 'AAPL'].", + ) + timeout_seconds: float = Field(default=10.0, description="Connection and market-data timeout in seconds.") + + def run(self): + if not package_available("ib_insync"): + return json.dumps( + { + "ok": False, + "error": "ib_insync is not installed. Install requirements.txt before using IBKR tools.", + }, + indent=2, + ) + + from ib_insync import IB, Stock # noqa: PLC0415 + + settings = ibkr_settings() + ib = IB() + try: + ib.connect(settings.host, settings.port, clientId=settings.client_id, timeout=self.timeout_seconds) + accounts = ib.managedAccounts() + account_values = [ + { + "account": value.account, + "tag": value.tag, + "value": value.value, + "currency": value.currency, + "model_code": value.modelCode, + } + for value in ib.accountSummary(account=settings.account or "") + ] + positions = [] + if self.include_positions: + positions = [ + { + "account": pos.account, + "symbol": pos.contract.symbol, + "sec_type": pos.contract.secType, + "exchange": pos.contract.exchange, + "currency": pos.contract.currency, + "position": pos.position, + "avg_cost": pos.avgCost, + } + for pos in ib.positions() + ] + open_orders = [] + if self.include_open_orders: + open_orders = [ + { + "order_id": trade.order.orderId, + "action": trade.order.action, + "quantity": trade.order.totalQuantity, + "order_type": trade.order.orderType, + "limit_price": trade.order.lmtPrice, + "status": trade.orderStatus.status, + "symbol": trade.contract.symbol, + } + for trade in ib.openTrades() + ] + probes = [] + for symbol in self.market_data_probe_symbols: + contract = Stock(symbol.upper(), "SMART", "USD") + ib.qualifyContracts(contract) + ticker = ib.reqMktData(contract, "", False, False) + ib.sleep(min(2.0, self.timeout_seconds)) + probes.append( + { + "symbol": symbol.upper(), + "last": ticker.last, + "bid": ticker.bid, + "ask": ticker.ask, + "close": ticker.close, + "market_price": ticker.marketPrice(), + "has_live_or_delayed_data": any( + value is not None and value == value + for value in (ticker.last, ticker.bid, ticker.ask, ticker.close) + ), + } + ) + ib.cancelMktData(contract) + return json.dumps( + { + "ok": True, + "checked_at": datetime.now(timezone.utc).isoformat(), + "host": settings.host, + "port": settings.port, + "accounts": accounts, + "selected_account": settings.account, + "account_values": account_values, + "positions": positions, + "open_orders": open_orders, + "market_data_probes": probes, + }, + indent=2, + default=str, + ) + except Exception as exc: # noqa: BLE001 + return json.dumps({"ok": False, "error": compact_error(exc)}, indent=2) + finally: + if ib.isConnected(): + ib.disconnect() diff --git a/trading_tools/ibkr_order_tool.py b/trading_tools/ibkr_order_tool.py new file mode 100644 index 00000000..4f4e7570 --- /dev/null +++ b/trading_tools/ibkr_order_tool.py @@ -0,0 +1,136 @@ +from __future__ import annotations + +import json +import os +from datetime import datetime, timezone + +from agency_swarm.tools import BaseTool +from pydantic import Field + +from .common import bool_env, compact_error, ibkr_settings, package_available + + +class IBKROrderTool(BaseTool): + """ + Previews or submits stock orders through Interactive Brokers. + + Defaults to preview mode. Live submission requires IBKR_ENABLE_LIVE_TRADING=true + and confirmation_token matching IBKR_TRADE_CONFIRMATION_TOKEN. + """ + + symbol: str = Field(..., description="Stock symbol, e.g. AAPL.") + action: str = Field(..., description="BUY or SELL.") + quantity: float = Field(..., description="Share quantity.") + order_type: str = Field(default="MKT", description="MKT, LMT, STP, or STP LMT.") + limit_price: float | None = Field(default=None, description="Limit price for LMT or STP LMT orders.") + stop_price: float | None = Field(default=None, description="Stop price for STP or STP LMT orders.") + time_in_force: str = Field(default="DAY", description="DAY, GTC, IOC, etc.") + submit: bool = Field(default=False, description="If false, only return an order preview.") + confirmation_token: str | None = Field( + default=None, + description="Required for live submission. Must match IBKR_TRADE_CONFIRMATION_TOKEN.", + ) + + def run(self): + if not package_available("ib_insync"): + return json.dumps( + { + "ok": False, + "error": "ib_insync is not installed. Install requirements.txt before using IBKR tools.", + }, + indent=2, + ) + from ib_insync import IB, LimitOrder, MarketOrder, Stock, StopLimitOrder, StopOrder # noqa: PLC0415 + + action = self.action.upper().strip() + order_type = self.order_type.upper().strip() + if action not in {"BUY", "SELL"}: + return json.dumps({"ok": False, "error": "action must be BUY or SELL"}, indent=2) + if self.quantity <= 0: + return json.dumps({"ok": False, "error": "quantity must be positive"}, indent=2) + + settings = ibkr_settings() + contract = Stock(self.symbol.upper().strip(), "SMART", "USD") + if order_type == "MKT": + order = MarketOrder(action, self.quantity, tif=self.time_in_force) + elif order_type == "LMT": + if self.limit_price is None: + return json.dumps({"ok": False, "error": "limit_price is required for LMT orders"}, indent=2) + order = LimitOrder(action, self.quantity, self.limit_price, tif=self.time_in_force) + elif order_type == "STP": + if self.stop_price is None: + return json.dumps({"ok": False, "error": "stop_price is required for STP orders"}, indent=2) + order = StopOrder(action, self.quantity, self.stop_price, tif=self.time_in_force) + elif order_type == "STP LMT": + if self.limit_price is None or self.stop_price is None: + return json.dumps( + {"ok": False, "error": "limit_price and stop_price are required for STP LMT orders"}, + indent=2, + ) + order = StopLimitOrder(action, self.quantity, self.limit_price, self.stop_price, tif=self.time_in_force) + else: + return json.dumps({"ok": False, "error": "order_type must be MKT, LMT, STP, or STP LMT"}, indent=2) + if settings.account: + order.account = settings.account + + preview = { + "symbol": contract.symbol, + "action": action, + "quantity": self.quantity, + "order_type": order_type, + "limit_price": self.limit_price, + "stop_price": self.stop_price, + "time_in_force": self.time_in_force, + "account": settings.account, + "submit_requested": self.submit, + "live_trading_enabled": bool_env("IBKR_ENABLE_LIVE_TRADING", False), + } + if not self.submit: + return json.dumps({"ok": True, "mode": "preview", "order": preview}, indent=2, default=str) + if not bool_env("IBKR_ENABLE_LIVE_TRADING", False): + return json.dumps( + { + "ok": False, + "mode": "blocked", + "reason": "Set IBKR_ENABLE_LIVE_TRADING=true to permit order submission.", + "order": preview, + }, + indent=2, + default=str, + ) + expected_token = os.getenv("IBKR_TRADE_CONFIRMATION_TOKEN") + if not expected_token or self.confirmation_token != expected_token: + return json.dumps( + { + "ok": False, + "mode": "blocked", + "reason": "confirmation_token did not match IBKR_TRADE_CONFIRMATION_TOKEN.", + "order": preview, + }, + indent=2, + default=str, + ) + + ib = IB() + try: + ib.connect(settings.host, settings.port, clientId=settings.client_id) + ib.qualifyContracts(contract) + trade = ib.placeOrder(contract, order) + ib.sleep(1) + return json.dumps( + { + "ok": True, + "mode": "submitted", + "submitted_at": datetime.now(timezone.utc).isoformat(), + "order": preview, + "broker_status": trade.orderStatus.status, + "order_id": trade.order.orderId, + }, + indent=2, + default=str, + ) + except Exception as exc: # noqa: BLE001 + return json.dumps({"ok": False, "error": compact_error(exc), "order": preview}, indent=2, default=str) + finally: + if ib.isConnected(): + ib.disconnect() diff --git a/trading_tools/market_data_snapshot.py b/trading_tools/market_data_snapshot.py new file mode 100644 index 00000000..585a8aaa --- /dev/null +++ b/trading_tools/market_data_snapshot.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import json +from datetime import datetime, timezone + +from agency_swarm.tools import BaseTool +from pydantic import Field + +from .common import compact_error, package_available + + +class MarketDataSnapshot(BaseTool): + """ + Fetches stock price history and fundamentals from the best available configured source. + + Currently uses yfinance when installed. Agents should run TradingCapabilityDetector first + to identify paid providers and IBKR availability for deeper market-data workflows. + """ + + symbols: list[str] = Field(..., description="Stock symbols to fetch, e.g. ['SPY', 'AAPL'].") + period: str = Field(default="1y", description="yfinance period such as 1d, 5d, 1mo, 6mo, 1y, 5y.") + interval: str = Field(default="1d", description="yfinance interval such as 1m, 5m, 1h, 1d, 1wk.") + include_info: bool = Field(default=False, description="Include selected fundamental metadata.") + + def run(self): + if not package_available("yfinance"): + return json.dumps( + {"ok": False, "error": "yfinance is not installed. Install requirements.txt to use this tool."}, + indent=2, + ) + import yfinance as yf # noqa: PLC0415 + + output = {"ok": True, "checked_at": datetime.now(timezone.utc).isoformat(), "symbols": {}} + try: + for symbol in self.symbols: + ticker = yf.Ticker(symbol.upper()) + hist = ticker.history(period=self.period, interval=self.interval, auto_adjust=False) + summary = { + "rows": int(len(hist)), + "start": str(hist.index.min()) if not hist.empty else None, + "end": str(hist.index.max()) if not hist.empty else None, + "last_close": float(hist["Close"].dropna().iloc[-1]) if not hist.empty else None, + "last_volume": int(hist["Volume"].dropna().iloc[-1]) if not hist.empty and "Volume" in hist else None, + "recent_bars": hist.tail(5).reset_index().to_dict(orient="records") if not hist.empty else [], + } + if self.include_info: + info = ticker.get_info() + summary["info"] = { + key: info.get(key) + for key in ( + "shortName", + "sector", + "industry", + "marketCap", + "trailingPE", + "forwardPE", + "dividendYield", + "beta", + ) + } + output["symbols"][symbol.upper()] = summary + return json.dumps(output, indent=2, default=str) + except Exception as exc: # noqa: BLE001 + return json.dumps({"ok": False, "error": compact_error(exc)}, indent=2) diff --git a/trading_tools/quant_backtest.py b/trading_tools/quant_backtest.py new file mode 100644 index 00000000..745e6c1a --- /dev/null +++ b/trading_tools/quant_backtest.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import json +import math +from datetime import datetime, timezone + +from agency_swarm.tools import BaseTool +from pydantic import Field + +from .common import compact_error, package_available + + +class QuantBacktest(BaseTool): + """ + Runs a simple long-only or long/flat moving-average momentum backtest for a stock symbol. + + This is a fast first-pass research tool for idea triage, not a production execution engine. + """ + + symbol: str = Field(..., description="Stock symbol to backtest.") + period: str = Field(default="5y", description="Data period, e.g. 1y, 2y, 5y, 10y.") + fast_window: int = Field(default=20, description="Fast moving average window.") + slow_window: int = Field(default=100, description="Slow moving average window.") + long_flat: bool = Field(default=True, description="If true, strategy is long or cash. If false, long or short.") + transaction_cost_bps: float = Field(default=2.0, description="One-way transaction cost in basis points.") + + def run(self): + if not package_available("yfinance"): + return json.dumps( + {"ok": False, "error": "yfinance is not installed. Install requirements.txt to use this tool."}, + indent=2, + ) + try: + import numpy as np # noqa: PLC0415 + import yfinance as yf # noqa: PLC0415 + + if self.fast_window <= 1 or self.slow_window <= self.fast_window: + return json.dumps( + {"ok": False, "error": "Require slow_window > fast_window > 1."}, + indent=2, + ) + data = yf.Ticker(self.symbol.upper()).history(period=self.period, interval="1d", auto_adjust=True) + if data.empty or len(data) < self.slow_window + 10: + return json.dumps({"ok": False, "error": "Not enough price history returned."}, indent=2) + + close = data["Close"].dropna() + returns = close.pct_change().fillna(0.0) + fast = close.rolling(self.fast_window).mean() + slow = close.rolling(self.slow_window).mean() + raw_signal = (fast > slow).astype(float) + if not self.long_flat: + raw_signal = raw_signal.replace(0.0, -1.0) + signal = raw_signal.shift(1).fillna(0.0) + turnover = signal.diff().abs().fillna(0.0) + cost = turnover * (self.transaction_cost_bps / 10000.0) + strategy_returns = signal * returns - cost + equity = (1 + strategy_returns).cumprod() + benchmark = (1 + returns).cumprod() + years = max((close.index[-1] - close.index[0]).days / 365.25, 0.001) + total_return = float(equity.iloc[-1] - 1) + benchmark_return = float(benchmark.iloc[-1] - 1) + cagr = float(equity.iloc[-1] ** (1 / years) - 1) + ann_vol = float(strategy_returns.std() * math.sqrt(252)) + sharpe = float((strategy_returns.mean() * 252) / ann_vol) if ann_vol > 0 else None + drawdown = equity / equity.cummax() - 1 + max_drawdown = float(drawdown.min()) + trades = int((turnover > 0).sum()) + win_rate = float((strategy_returns[strategy_returns != 0] > 0).mean()) + + return json.dumps( + { + "ok": True, + "checked_at": datetime.now(timezone.utc).isoformat(), + "symbol": self.symbol.upper(), + "period": self.period, + "fast_window": self.fast_window, + "slow_window": self.slow_window, + "mode": "long_flat" if self.long_flat else "long_short", + "transaction_cost_bps": self.transaction_cost_bps, + "rows": int(len(close)), + "start": str(close.index[0]), + "end": str(close.index[-1]), + "strategy": { + "total_return": total_return, + "cagr": cagr, + "annualized_volatility": ann_vol, + "sharpe": sharpe, + "max_drawdown": max_drawdown, + "trades": trades, + "win_rate_nonzero_days": win_rate if not np.isnan(win_rate) else None, + }, + "benchmark_buy_hold": {"total_return": benchmark_return}, + "current_signal": "long" if signal.iloc[-1] > 0 else ("short" if signal.iloc[-1] < 0 else "cash"), + }, + indent=2, + default=str, + ) + except Exception as exc: # noqa: BLE001 + return json.dumps({"ok": False, "error": compact_error(exc)}, indent=2)