From d4fa1881fe0777c91c18d5c8d3a97013519c0cd9 Mon Sep 17 00:00:00 2001 From: marcvanduyn Date: Wed, 15 Apr 2026 17:31:21 +0200 Subject: [PATCH 1/3] feat: add strategy_ids and tag filtering to all applicable MCP tools - Add _resolve_backtests() helper: filters backtests by strategy_ids and/or tag - Add _resolve_sids() helper: resolves strategy_ids from strategy_id, strategy_ids, or tag - Add strategy_ids + tag params to 7 multi-strategy tools: list_strategies, rank_strategies, get_full_analysis, get_trading_activity, get_correlation_matrix, get_window_coverage, filter_strategies - Add tag param to 6 time-series tools (already had strategy_ids): get_equity_curve, get_drawdown_series, get_monthly_returns, get_yearly_returns, get_rolling_sharpe, get_portfolio_snapshots - 18 of 23 tools now support tag/strategy_ids filtering - Single-strategy tools (5) and note tools (5) unchanged --- .../cli/mcp_server.py | 298 ++++++++++++++---- 1 file changed, 241 insertions(+), 57 deletions(-) diff --git a/investing_algorithm_framework/cli/mcp_server.py b/investing_algorithm_framework/cli/mcp_server.py index fd9f710c..749d17c2 100644 --- a/investing_algorithm_framework/cli/mcp_server.py +++ b/investing_algorithm_framework/cli/mcp_server.py @@ -1025,11 +1025,28 @@ def _get_tools(self): "description": ( "List all backtest strategies with their key summary " "metrics (CAGR, Sharpe, Max DD, Win Rate, etc.). " - "Use this first to get an overview." + "Use this first to get an overview. " + "Optionally filter by strategy_ids or tag." ), "inputSchema": { "type": "object", - "properties": {}, + "properties": { + "strategy_ids": { + "type": "array", + "items": {"type": "string"}, + "description": ( + "Optional: filter to specific " + "strategy algorithm_ids." + ), + }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, + }, }, }, { @@ -1052,12 +1069,28 @@ def _get_tools(self): { "name": "rank_strategies", "description": ( - "Rank all strategies by a specific metric. " - "Useful for finding the best/worst performers." + "Rank strategies by a specific metric. " + "Useful for finding the best/worst performers. " + "Optionally filter by strategy_ids or tag." ), "inputSchema": { "type": "object", "properties": { + "strategy_ids": { + "type": "array", + "items": {"type": "string"}, + "description": ( + "Optional: filter to specific " + "strategy algorithm_ids." + ), + }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, "metric": { "type": "string", "description": ( @@ -1104,26 +1137,60 @@ def _get_tools(self): "description": ( "Get a complete analysis document with all strategies, " "metrics, rankings, per-window breakdowns and top trades. " - "Use this for comprehensive analysis." + "Use this for comprehensive analysis. " + "Optionally filter by strategy_ids or tag." ), "inputSchema": { "type": "object", - "properties": {}, + "properties": { + "strategy_ids": { + "type": "array", + "items": {"type": "string"}, + "description": ( + "Optional: filter to specific " + "strategy algorithm_ids." + ), + }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, + }, }, }, { "name": "get_trading_activity", "description": ( - "Get a Trading Activity table for all strategies with " + "Get a Trading Activity table with " "12 trading metrics: Profit Factor, Win Rate, " "Trades/yr, Trades/mo, Trades/wk, # Trades, " "Avg Return, Median Return, Avg Duration, Win Streak, " "Loss Streak, % Win Months. Matches the dashboard " - "Trading Activity view exactly." + "Trading Activity view exactly. " + "Optionally filter by strategy_ids or tag." ), "inputSchema": { "type": "object", - "properties": {}, + "properties": { + "strategy_ids": { + "type": "array", + "items": {"type": "string"}, + "description": ( + "Optional: filter to specific " + "strategy algorithm_ids." + ), + }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, + }, }, }, { @@ -1156,7 +1223,7 @@ def _get_tools(self): "for a stacked multi-strategy comparison. Sampled to " "~50 points. Shows date, value, and cumulative " "growth %. Use window to filter by a specific " - "backtest window." + "backtest window. Use tag to filter by batch." ), "inputSchema": { "type": "object", @@ -1177,6 +1244,13 @@ def _get_tools(self): "strategy_id, not both." ), }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, "window": { "type": "string", "description": ( @@ -1192,7 +1266,7 @@ def _get_tools(self): "Get drawdown time-series for one or more strategies " "(how far below peak at each point). Use strategy_id " "for one, or strategy_ids for a stacked comparison. " - "Sampled to ~50 pts." + "Sampled to ~50 pts. Use tag to filter by batch." ), "inputSchema": { "type": "object", @@ -1212,6 +1286,13 @@ def _get_tools(self): "stacked comparison." ), }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, "window": { "type": "string", "description": "Optional: specific window name", @@ -1225,7 +1306,8 @@ def _get_tools(self): "Get monthly returns heatmap for one or more " "strategies. Use strategy_id for one, or " "strategy_ids for sequential tables per strategy. " - "Great for seasonality analysis." + "Great for seasonality analysis. " + "Use tag to filter by batch." ), "inputSchema": { "type": "object", @@ -1245,6 +1327,13 @@ def _get_tools(self): "comparison." ), }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, "window": { "type": "string", "description": "Optional: specific window name", @@ -1257,7 +1346,7 @@ def _get_tools(self): "description": ( "Get yearly returns for one or more strategies. " "Use strategy_id for one, or strategy_ids for a " - "stacked comparison table." + "stacked comparison table. Use tag to filter by batch." ), "inputSchema": { "type": "object", @@ -1277,6 +1366,13 @@ def _get_tools(self): "stacked comparison." ), }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, "window": { "type": "string", "description": "Optional: specific window name", @@ -1290,7 +1386,8 @@ def _get_tools(self): "Get rolling Sharpe ratio time-series for one or more " "strategies. Use strategy_id for one, or strategy_ids " "for a stacked comparison. Shows how risk-adjusted " - "performance evolves over time." + "performance evolves over time. " + "Use tag to filter by batch." ), "inputSchema": { "type": "object", @@ -1310,6 +1407,13 @@ def _get_tools(self): "stacked comparison." ), }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, "window": { "type": "string", "description": "Optional: specific window name", @@ -1358,11 +1462,27 @@ def _get_tools(self): "description": ( "Get cross-strategy return correlation matrix. " "Shows how correlated different strategies are — " - "useful for portfolio construction." + "useful for portfolio construction. " + "Optionally filter by strategy_ids or tag." ), "inputSchema": { "type": "object", "properties": { + "strategy_ids": { + "type": "array", + "items": {"type": "string"}, + "description": ( + "Optional: filter to specific " + "strategy algorithm_ids." + ), + }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, "window": { "type": "string", "description": ( @@ -1377,11 +1497,28 @@ def _get_tools(self): "name": "get_window_coverage", "description": ( "Get a summary of all backtest windows — dates, " - "duration, and how many strategies ran in each." + "duration, and how many strategies ran in each. " + "Optionally filter by strategy_ids or tag." ), "inputSchema": { "type": "object", - "properties": {}, + "properties": { + "strategy_ids": { + "type": "array", + "items": {"type": "string"}, + "description": ( + "Optional: filter to specific " + "strategy algorithm_ids." + ), + }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, + }, }, }, { @@ -1390,7 +1527,8 @@ def _get_tools(self): "Get portfolio value snapshots for one or more " "strategies — initial value, final value, net gain, " "and growth per window. Use strategy_id for one, or " - "strategy_ids for a stacked comparison." + "strategy_ids for a stacked comparison. " + "Use tag to filter by batch." ), "inputSchema": { "type": "object", @@ -1410,6 +1548,13 @@ def _get_tools(self): "stacked comparison." ), }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name." + ), + }, "window": { "type": "string", "description": "Optional: specific window name", @@ -1569,11 +1714,29 @@ def _get_tools(self): "description": ( "Filter strategies by metric conditions. " "Returns strategies matching ALL conditions. " - "Example: Sharpe > 1.0 AND Max DD < 0.15" + "Example: Sharpe > 1.0 AND Max DD < 0.15. " + "Optionally pre-filter by strategy_ids or tag." ), "inputSchema": { "type": "object", "properties": { + "strategy_ids": { + "type": "array", + "items": {"type": "string"}, + "description": ( + "Optional: filter to specific " + "strategy algorithm_ids " + "before applying conditions." + ), + }, + "tag": { + "type": "string", + "description": ( + "Optional: filter to strategies " + "with this tag/batch name " + "before applying conditions." + ), + }, "conditions": { "type": "array", "description": ( @@ -1612,10 +1775,43 @@ def _get_tools(self): }, ] + def _resolve_backtests(self, arguments): + """Filter backtests by strategy_ids and/or tag.""" + backtests = self._backtests + tag = arguments.get("tag") + if tag: + backtests = [ + bt for bt in backtests + if self._bt_tags.get(bt.algorithm_id) == tag + ] + sids = arguments.get("strategy_ids") + if sids: + sid_set = set(sids) + backtests = [ + bt for bt in backtests + if bt.algorithm_id in sid_set + ] + return backtests + + def _resolve_sids(self, arguments): + """Resolve strategy_ids from strategy_id, strategy_ids, or tag.""" + sids = arguments.get("strategy_ids") or [] + sid = arguments.get("strategy_id", "") + tag = arguments.get("tag") + if tag and not sids and not sid: + sids = [ + bt.algorithm_id for bt in self._backtests + if self._bt_tags.get(bt.algorithm_id) == tag + ] + if sid and not sids: + sids = [sid] + return sids + def _handle_tool_call(self, name, arguments): self._ensure_loaded() if name == "list_strategies": + backtests = self._resolve_backtests(arguments) has_tags = any(self._bt_tags.values()) lines = ["# Strategies Overview\n"] if has_tags: @@ -1644,7 +1840,7 @@ def _handle_tool_call(self, name, arguments): "|----------|---------------" "|-----------|------------|" ) - for bt in self._backtests: + for bt in backtests: s = bt.backtest_summary tag = self._bt_tags.get( bt.algorithm_id, '' @@ -1726,10 +1922,11 @@ def _handle_tool_call(self, name, arguments): return md elif name == "rank_strategies": + backtests = self._resolve_backtests(arguments) metric = arguments.get("metric", "sharpe_ratio") ascending = arguments.get("ascending", False) return _ranking_table( - self._backtests, metric, ascending, + backtests, metric, ascending, tags=self._bt_tags, ) @@ -1782,13 +1979,15 @@ def _handle_tool_call(self, name, arguments): return md elif name == "get_full_analysis": + backtests = self._resolve_backtests(arguments) return _full_analysis( - self._backtests, tags=self._bt_tags + backtests, tags=self._bt_tags ) elif name == "get_trading_activity": + backtests = self._resolve_backtests(arguments) return _trading_activity_table( - self._backtests, tags=self._bt_tags + backtests, tags=self._bt_tags ) elif name == "get_trades": @@ -1821,12 +2020,9 @@ def _handle_tool_call(self, name, arguments): elif name == "get_equity_curve": window = arguments.get("window") - sids = arguments.get("strategy_ids") or [] - sid = arguments.get("strategy_id", "") - if sid and not sids: - sids = [sid] + sids = self._resolve_sids(arguments) if not sids: - return "Provide strategy_id or strategy_ids." + return "Provide strategy_id, strategy_ids, or tag." missing = [s for s in sids if s not in self._bt_map] if missing: return ( @@ -1846,12 +2042,9 @@ def _handle_tool_call(self, name, arguments): elif name == "get_drawdown_series": window = arguments.get("window") - sids = arguments.get("strategy_ids") or [] - sid = arguments.get("strategy_id", "") - if sid and not sids: - sids = [sid] + sids = self._resolve_sids(arguments) if not sids: - return "Provide strategy_id or strategy_ids." + return "Provide strategy_id, strategy_ids, or tag." missing = [s for s in sids if s not in self._bt_map] if missing: return ( @@ -1870,12 +2063,9 @@ def _handle_tool_call(self, name, arguments): elif name == "get_monthly_returns": window = arguments.get("window") - sids = arguments.get("strategy_ids") or [] - sid = arguments.get("strategy_id", "") - if sid and not sids: - sids = [sid] + sids = self._resolve_sids(arguments) if not sids: - return "Provide strategy_id or strategy_ids." + return "Provide strategy_id, strategy_ids, or tag." missing = [s for s in sids if s not in self._bt_map] if missing: return ( @@ -1894,12 +2084,9 @@ def _handle_tool_call(self, name, arguments): elif name == "get_yearly_returns": window = arguments.get("window") - sids = arguments.get("strategy_ids") or [] - sid = arguments.get("strategy_id", "") - if sid and not sids: - sids = [sid] + sids = self._resolve_sids(arguments) if not sids: - return "Provide strategy_id or strategy_ids." + return "Provide strategy_id, strategy_ids, or tag." missing = [s for s in sids if s not in self._bt_map] if missing: return ( @@ -1918,12 +2105,9 @@ def _handle_tool_call(self, name, arguments): elif name == "get_rolling_sharpe": window = arguments.get("window") - sids = arguments.get("strategy_ids") or [] - sid = arguments.get("strategy_id", "") - if sid and not sids: - sids = [sid] + sids = self._resolve_sids(arguments) if not sids: - return "Provide strategy_id or strategy_ids." + return "Provide strategy_id, strategy_ids, or tag." missing = [s for s in sids if s not in self._bt_map] if missing: return ( @@ -1966,24 +2150,23 @@ def _handle_tool_call(self, name, arguments): return f"# Return Scenarios — {sid}\n\n" + _return_scenarios(bt) elif name == "get_correlation_matrix": + backtests = self._resolve_backtests(arguments) window = arguments.get("window") return "# Correlation Matrix\n\n" + _correlation_matrix( - self._backtests, window=window + backtests, window=window ) elif name == "get_window_coverage": + backtests = self._resolve_backtests(arguments) return "# Window Coverage\n\n" + _window_coverage( - self._backtests + backtests ) elif name == "get_portfolio_snapshots": window = arguments.get("window") - sids = arguments.get("strategy_ids") or [] - sid = arguments.get("strategy_id", "") - if sid and not sids: - sids = [sid] + sids = self._resolve_sids(arguments) if not sids: - return "Provide strategy_id or strategy_ids." + return "Provide strategy_id, strategy_ids, or tag." missing = [s for s in sids if s not in self._bt_map] if missing: return ( @@ -2148,13 +2331,14 @@ def _handle_tool_call(self, name, arguments): return f"Note {note_id} deleted." elif name == "filter_strategies": + backtests = self._resolve_backtests(arguments) conditions = arguments.get("conditions", []) if not conditions: return "No conditions provided." - matched = _filter_strategies(self._backtests, conditions) + matched = _filter_strategies(backtests, conditions) if not matched: return "No strategies match all conditions." - total = len(self._backtests) + total = len(backtests) md = ( f"# Filtered Strategies" f" ({len(matched)} of {total})\n\n" From e7716a19122e8280fd864b4019bb19c5d2d416a2 Mon Sep 17 00:00:00 2001 From: marcvanduyn Date: Wed, 15 Apr 2026 17:43:34 +0200 Subject: [PATCH 2/3] feat: add get_orders and get_positions MCP tools - Add get_orders tool: lists all orders for a strategy with symbol, side, type, status, price, amount, filled, cost, fee, fee_rate, slippage, created date. Supports window filter and limit parameter. - Add get_positions tool: lists all positions for a strategy with symbol, amount, cost. Supports window filter. - Add _all_orders() and _all_positions() helper functions - Update tutorial notebook: 23 -> 25 tools, updated tool tables with strategy_ids/tag support columns, added orders/positions to docs --- .../09_report_and_llm_workflow.ipynb | 59 +++--- .../cli/mcp_server.py | 182 ++++++++++++++++++ 2 files changed, 215 insertions(+), 26 deletions(-) diff --git a/examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb b/examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb index f84bf4e1..919c4f41 100644 --- a/examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb +++ b/examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb @@ -265,7 +265,7 @@ "```\n", "\n", "Once running, your LLM in Copilot Chat (or any MCP client) automatically\n", - "discovers **23 tools** it can call." + "discovers **25 tools** it can call." ] }, { @@ -273,38 +273,45 @@ "id": "11", "metadata": {}, "source": [ - "## Step 4 — The LLM Toolkit (23 MCP Tools)\n", + "## Step 4 — The LLM Toolkit (25 MCP Tools)\n", "\n", "When the MCP server starts, the LLM gets access to these tool categories:\n", "\n", "### Data Exploration\n", + "| Tool | What it does | Supports `strategy_ids`? | Supports `tag`? |\n", + "|------|-------------|:---:|:---:|\n", + "| `list_strategies` | List all strategies with key metrics | ✅ | ✅ |\n", + "| `get_strategy_details` | Deep-dive into one strategy (all metrics, parameters) | — | — |\n", + "| `rank_strategies` | Rank by any metric (CAGR, Sharpe, max drawdown, etc.) | ✅ | ✅ |\n", + "| `compare_strategies` | Side-by-side comparison of 2 strategies | — | — |\n", + "| `get_full_analysis` | Complete data dump for AI analysis | ✅ | ✅ |\n", + "| `get_trading_activity` | Trading Activity table — all 12 trading metrics | ✅ | ✅ |\n", + "\n", + "### Time Series (all support multi-strategy stacking + window filter)\n", + "| Tool | What it does | Supports `strategy_ids`? | Supports `tag`? |\n", + "|------|-------------|:---:|:---:|\n", + "| `get_equity_curve` | Equity curve (portfolio value over time) | ✅ | ✅ |\n", + "| `get_drawdown_series` | Drawdown over time | ✅ | ✅ |\n", + "| `get_monthly_returns` | Monthly return heatmap grid | ✅ | ✅ |\n", + "| `get_yearly_returns` | Yearly return summary | ✅ | ✅ |\n", + "| `get_rolling_sharpe` | Rolling Sharpe ratio series | ✅ | ✅ |\n", + "| `get_correlation_matrix` | Strategy correlation matrix | ✅ | ✅ |\n", + "| `get_window_coverage` | Backtest window coverage info | ✅ | ✅ |\n", + "| `get_portfolio_snapshots` | Portfolio initial/final value & growth | ✅ | ✅ |\n", + "\n", + "### Trades, Orders & Positions (single strategy)\n", "| Tool | What it does |\n", "|------|-------------|\n", - "| `list_strategies` | List all strategies with key metrics |\n", - "| `get_strategy_details` | Deep-dive into one strategy (all metrics, parameters) |\n", - "| `rank_strategies` | Rank by any metric (CAGR, Sharpe, max drawdown, etc.) |\n", - "| `compare_strategies` | Side-by-side comparison of 2+ strategies |\n", - "| `get_full_analysis` | Complete data dump for AI analysis |\n", - "| `get_trading_activity` | Trading Activity table — all 12 trading metrics for every strategy |\n", - "\n", - "### Time Series & Trades (all support multi-strategy stacking + window filter)\n", - "| Tool | What it does | Multi-strategy? |\n", - "|------|-------------|:---:|\n", - "| `get_equity_curve` | Equity curve (portfolio value over time) | ✅ stacked table |\n", - "| `get_drawdown_series` | Drawdown over time | ✅ stacked table |\n", - "| `get_monthly_returns` | Monthly return heatmap grid | ✅ sequential per strategy |\n", - "| `get_yearly_returns` | Yearly return summary | ✅ stacked table |\n", - "| `get_rolling_sharpe` | Rolling Sharpe ratio series | ✅ stacked table |\n", - "| `get_trades` | All trades with entry/exit prices, P&L | single |\n", - "| `get_symbol_breakdown` | Per-symbol performance breakdown | single |\n", - "| `get_return_scenarios` | Best/worst case return scenarios | single |\n", - "| `get_correlation_matrix` | Strategy correlation matrix | all strategies |\n", - "| `get_window_coverage` | Backtest window coverage info | all strategies |\n", - "| `get_portfolio_snapshots` | Portfolio initial/final value & growth | ✅ stacked table |\n", + "| `get_trades` | All trades with entry/exit prices, P&L (top N by return magnitude) |\n", + "| `get_orders` | All orders with side, type, status, price, amount, fee, slippage |\n", + "| `get_positions` | All positions with symbol, amount, cost |\n", + "| `get_symbol_breakdown` | Per-symbol performance breakdown |\n", + "| `get_return_scenarios` | Best/worst case return scenarios |\n", "\n", "**Multi-strategy usage:** Pass `strategy_ids: [\"strat_A\", \"strat_B\", \"strat_C\"]`\n", - "instead of `strategy_id` to get a stacked comparison. Add `window: \"2024\"` to\n", - "filter to a specific backtest window.\n", + "instead of `strategy_id` to get a stacked comparison. Add `window: \"2024\"`\n", + "to filter to a specific backtest window. Or use `tag: \"experiment_A\"`\n", + "to filter by batch tag.\n", "\n", "### Notes & Report Building\n", "| Tool | What it does |\n", @@ -314,7 +321,7 @@ "| `get_note` | Read a note (shows snapshot IDs for embedding) |\n", "| `update_note` | Update note content, selections, or embed snapshots |\n", "| `delete_note` | Remove a note |\n", - "| `filter_strategies` | Apply a note's keep/maybe/reject selections to the dashboard |" + "| `filter_strategies` | Filter strategies by metric conditions (supports `strategy_ids` + `tag` pre-filtering) |" ] }, { diff --git a/investing_algorithm_framework/cli/mcp_server.py b/investing_algorithm_framework/cli/mcp_server.py index 749d17c2..2655b2c4 100644 --- a/investing_algorithm_framework/cli/mcp_server.py +++ b/investing_algorithm_framework/cli/mcp_server.py @@ -241,6 +241,62 @@ def _top_trades(bt, n=10): return all_trades[:n] +def _all_orders(bt, window=None, limit=50): + """Get all orders for a strategy across windows.""" + orders = [] + for run in bt.get_all_backtest_runs(): + if window and run.backtest_date_range_name != window: + continue + wname = run.backtest_date_range_name or "—" + for o in (run.orders or []): + price = getattr(o, 'price', 0) or 0 + amount = getattr(o, 'amount', 0) or 0 + filled = getattr(o, 'filled', 0) or 0 + fee = getattr(o, 'order_fee', 0) or 0 + fee_rate = getattr(o, 'order_fee_rate', 0) or 0 + slippage = getattr(o, 'slippage', 0) or 0 + created = getattr(o, 'created_at', None) + orders.append({ + "symbol": getattr(o, 'target_symbol', '—'), + "side": str(getattr(o, 'order_side', '—')), + "type": str(getattr(o, 'order_type', '—')), + "status": str(getattr(o, 'status', '—')), + "price": round(float(price), 4), + "amount": round(float(amount), 6), + "filled": round(float(filled), 6), + "cost": round(float(amount) * float(price), 2), + "fee": round(float(fee), 4), + "fee_rate": round(float(fee_rate), 4), + "slippage": round(float(slippage), 4), + "created": _fmt_date(created), + "window": wname, + }) + orders.sort( + key=lambda x: x["created"] if x["created"] != "—" else "", + reverse=True, + ) + return orders[:limit] + + +def _all_positions(bt, window=None): + """Get all positions for a strategy across windows.""" + positions = [] + for run in bt.get_all_backtest_runs(): + if window and run.backtest_date_range_name != window: + continue + wname = run.backtest_date_range_name or "—" + for p in (run.positions or []): + amount = getattr(p, 'amount', 0) or 0 + cost = getattr(p, 'cost', 0) or 0 + positions.append({ + "symbol": getattr(p, 'symbol', '—'), + "amount": round(float(amount), 6), + "cost": round(float(cost), 2), + "window": wname, + }) + return positions + + def _full_analysis(backtests, tags=None): """Generate a complete analysis markdown document.""" has_tags = tags and any(tags.values()) @@ -1214,6 +1270,66 @@ def _get_tools(self): "required": ["strategy_id"], }, }, + { + "name": "get_orders", + "description": ( + "Get all orders for a strategy — symbol, side, type, " + "status, price, amount, filled, cost, fee, fee_rate, " + "slippage, and creation date. Optionally filter by " + "window. Sorted by date (newest first)." + ), + "inputSchema": { + "type": "object", + "properties": { + "strategy_id": { + "type": "string", + "description": ( + "The algorithm_id of the strategy" + ), + }, + "window": { + "type": "string", + "description": ( + "Optional: specific window name " + "to filter by" + ), + }, + "limit": { + "type": "integer", + "description": ( + "Max orders to return (default 50)" + ), + }, + }, + "required": ["strategy_id"], + }, + }, + { + "name": "get_positions", + "description": ( + "Get all positions for a strategy — symbol, amount, " + "and cost. Optionally filter by window." + ), + "inputSchema": { + "type": "object", + "properties": { + "strategy_id": { + "type": "string", + "description": ( + "The algorithm_id of the strategy" + ), + }, + "window": { + "type": "string", + "description": ( + "Optional: specific window name " + "to filter by" + ), + }, + }, + "required": ["strategy_id"], + }, + }, { "name": "get_equity_curve", "description": ( @@ -2018,6 +2134,72 @@ def _handle_tool_call(self, name, arguments): ) return md + elif name == "get_orders": + sid = arguments.get("strategy_id", "") + bt = self._bt_map.get(sid) + if not bt: + return f"Strategy '{sid}' not found." + window = arguments.get("window") + limit = arguments.get("limit", 50) + orders = _all_orders(bt, window=window, limit=limit) + if not orders: + return "No orders found." + md = f"# Orders for {sid}\n\n" + md += ( + "| Symbol | Side | Type | Status" + " | Price | Amount | Filled" + " | Cost | Fee | Fee Rate" + " | Slippage | Created | Window |\n" + ) + md += ( + "|--------|------|------|--------" + "|-------|--------|--------" + "|------|-----|----------" + "|----------|---------|--------|\n" + ) + for o in orders: + md += ( + f"| {o['symbol']} " + f"| {o['side']} " + f"| {o['type']} " + f"| {o['status']} " + f"| {o['price']} " + f"| {o['amount']} " + f"| {o['filled']} " + f"| {o['cost']} " + f"| {o['fee']} " + f"| {o['fee_rate']} " + f"| {o['slippage']} " + f"| {o['created']} " + f"| {o['window']} |\n" + ) + md += ( + f"\n*Showing {len(orders)} orders" + f" (limit: {limit})*\n" + ) + return md + + elif name == "get_positions": + sid = arguments.get("strategy_id", "") + bt = self._bt_map.get(sid) + if not bt: + return f"Strategy '{sid}' not found." + window = arguments.get("window") + positions = _all_positions(bt, window=window) + if not positions: + return "No positions found." + md = f"# Positions for {sid}\n\n" + md += "| Symbol | Amount | Cost | Window |\n" + md += "|--------|--------|------|--------|\n" + for p in positions: + md += ( + f"| {p['symbol']} " + f"| {p['amount']} " + f"| {p['cost']} " + f"| {p['window']} |\n" + ) + return md + elif name == "get_equity_curve": window = arguments.get("window") sids = self._resolve_sids(arguments) From 011a6c54a38f310135eeae66833ba23085bcdf15 Mon Sep 17 00:00:00 2001 From: marcvanduyn Date: Wed, 15 Apr 2026 17:48:29 +0200 Subject: [PATCH 3/3] docs: update notebook with get_orders, get_positions and tag/strategy_ids filtering docs --- examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb b/examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb index 919c4f41..2ef563cc 100644 --- a/examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb +++ b/examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb @@ -303,8 +303,8 @@ "| Tool | What it does |\n", "|------|-------------|\n", "| `get_trades` | All trades with entry/exit prices, P&L (top N by return magnitude) |\n", - "| `get_orders` | All orders with side, type, status, price, amount, fee, slippage |\n", - "| `get_positions` | All positions with symbol, amount, cost |\n", + "| `get_orders` | All orders with side, type, status, price, amount, filled, cost, fee, fee_rate, slippage. Supports `window` filter and `limit` (default 50) |\n", + "| `get_positions` | All positions with symbol, amount, cost. Supports `window` filter |\n", "| `get_symbol_breakdown` | Per-symbol performance breakdown |\n", "| `get_return_scenarios` | Best/worst case return scenarios |\n", "\n",