Skip to content

Commit e7716a1

Browse files
committed
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
1 parent d4fa188 commit e7716a1

2 files changed

Lines changed: 215 additions & 26 deletions

File tree

examples/tutorial/notebooks/09_report_and_llm_workflow.ipynb

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -265,46 +265,53 @@
265265
"```\n",
266266
"\n",
267267
"Once running, your LLM in Copilot Chat (or any MCP client) automatically\n",
268-
"discovers **23 tools** it can call."
268+
"discovers **25 tools** it can call."
269269
]
270270
},
271271
{
272272
"cell_type": "markdown",
273273
"id": "11",
274274
"metadata": {},
275275
"source": [
276-
"## Step 4 — The LLM Toolkit (23 MCP Tools)\n",
276+
"## Step 4 — The LLM Toolkit (25 MCP Tools)\n",
277277
"\n",
278278
"When the MCP server starts, the LLM gets access to these tool categories:\n",
279279
"\n",
280280
"### Data Exploration\n",
281+
"| Tool | What it does | Supports `strategy_ids`? | Supports `tag`? |\n",
282+
"|------|-------------|:---:|:---:|\n",
283+
"| `list_strategies` | List all strategies with key metrics | ✅ | ✅ |\n",
284+
"| `get_strategy_details` | Deep-dive into one strategy (all metrics, parameters) | — | — |\n",
285+
"| `rank_strategies` | Rank by any metric (CAGR, Sharpe, max drawdown, etc.) | ✅ | ✅ |\n",
286+
"| `compare_strategies` | Side-by-side comparison of 2 strategies | — | — |\n",
287+
"| `get_full_analysis` | Complete data dump for AI analysis | ✅ | ✅ |\n",
288+
"| `get_trading_activity` | Trading Activity table — all 12 trading metrics | ✅ | ✅ |\n",
289+
"\n",
290+
"### Time Series (all support multi-strategy stacking + window filter)\n",
291+
"| Tool | What it does | Supports `strategy_ids`? | Supports `tag`? |\n",
292+
"|------|-------------|:---:|:---:|\n",
293+
"| `get_equity_curve` | Equity curve (portfolio value over time) | ✅ | ✅ |\n",
294+
"| `get_drawdown_series` | Drawdown over time | ✅ | ✅ |\n",
295+
"| `get_monthly_returns` | Monthly return heatmap grid | ✅ | ✅ |\n",
296+
"| `get_yearly_returns` | Yearly return summary | ✅ | ✅ |\n",
297+
"| `get_rolling_sharpe` | Rolling Sharpe ratio series | ✅ | ✅ |\n",
298+
"| `get_correlation_matrix` | Strategy correlation matrix | ✅ | ✅ |\n",
299+
"| `get_window_coverage` | Backtest window coverage info | ✅ | ✅ |\n",
300+
"| `get_portfolio_snapshots` | Portfolio initial/final value & growth | ✅ | ✅ |\n",
301+
"\n",
302+
"### Trades, Orders & Positions (single strategy)\n",
281303
"| Tool | What it does |\n",
282304
"|------|-------------|\n",
283-
"| `list_strategies` | List all strategies with key metrics |\n",
284-
"| `get_strategy_details` | Deep-dive into one strategy (all metrics, parameters) |\n",
285-
"| `rank_strategies` | Rank by any metric (CAGR, Sharpe, max drawdown, etc.) |\n",
286-
"| `compare_strategies` | Side-by-side comparison of 2+ strategies |\n",
287-
"| `get_full_analysis` | Complete data dump for AI analysis |\n",
288-
"| `get_trading_activity` | Trading Activity table — all 12 trading metrics for every strategy |\n",
289-
"\n",
290-
"### Time Series & Trades (all support multi-strategy stacking + window filter)\n",
291-
"| Tool | What it does | Multi-strategy? |\n",
292-
"|------|-------------|:---:|\n",
293-
"| `get_equity_curve` | Equity curve (portfolio value over time) | ✅ stacked table |\n",
294-
"| `get_drawdown_series` | Drawdown over time | ✅ stacked table |\n",
295-
"| `get_monthly_returns` | Monthly return heatmap grid | ✅ sequential per strategy |\n",
296-
"| `get_yearly_returns` | Yearly return summary | ✅ stacked table |\n",
297-
"| `get_rolling_sharpe` | Rolling Sharpe ratio series | ✅ stacked table |\n",
298-
"| `get_trades` | All trades with entry/exit prices, P&L | single |\n",
299-
"| `get_symbol_breakdown` | Per-symbol performance breakdown | single |\n",
300-
"| `get_return_scenarios` | Best/worst case return scenarios | single |\n",
301-
"| `get_correlation_matrix` | Strategy correlation matrix | all strategies |\n",
302-
"| `get_window_coverage` | Backtest window coverage info | all strategies |\n",
303-
"| `get_portfolio_snapshots` | Portfolio initial/final value & growth | ✅ stacked table |\n",
305+
"| `get_trades` | All trades with entry/exit prices, P&L (top N by return magnitude) |\n",
306+
"| `get_orders` | All orders with side, type, status, price, amount, fee, slippage |\n",
307+
"| `get_positions` | All positions with symbol, amount, cost |\n",
308+
"| `get_symbol_breakdown` | Per-symbol performance breakdown |\n",
309+
"| `get_return_scenarios` | Best/worst case return scenarios |\n",
304310
"\n",
305311
"**Multi-strategy usage:** Pass `strategy_ids: [\"strat_A\", \"strat_B\", \"strat_C\"]`\n",
306-
"instead of `strategy_id` to get a stacked comparison. Add `window: \"2024\"` to\n",
307-
"filter to a specific backtest window.\n",
312+
"instead of `strategy_id` to get a stacked comparison. Add `window: \"2024\"`\n",
313+
"to filter to a specific backtest window. Or use `tag: \"experiment_A\"`\n",
314+
"to filter by batch tag.\n",
308315
"\n",
309316
"### Notes & Report Building\n",
310317
"| Tool | What it does |\n",
@@ -314,7 +321,7 @@
314321
"| `get_note` | Read a note (shows snapshot IDs for embedding) |\n",
315322
"| `update_note` | Update note content, selections, or embed snapshots |\n",
316323
"| `delete_note` | Remove a note |\n",
317-
"| `filter_strategies` | Apply a note's keep/maybe/reject selections to the dashboard |"
324+
"| `filter_strategies` | Filter strategies by metric conditions (supports `strategy_ids` + `tag` pre-filtering) |"
318325
]
319326
},
320327
{

investing_algorithm_framework/cli/mcp_server.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,62 @@ def _top_trades(bt, n=10):
241241
return all_trades[:n]
242242

243243

244+
def _all_orders(bt, window=None, limit=50):
245+
"""Get all orders for a strategy across windows."""
246+
orders = []
247+
for run in bt.get_all_backtest_runs():
248+
if window and run.backtest_date_range_name != window:
249+
continue
250+
wname = run.backtest_date_range_name or "—"
251+
for o in (run.orders or []):
252+
price = getattr(o, 'price', 0) or 0
253+
amount = getattr(o, 'amount', 0) or 0
254+
filled = getattr(o, 'filled', 0) or 0
255+
fee = getattr(o, 'order_fee', 0) or 0
256+
fee_rate = getattr(o, 'order_fee_rate', 0) or 0
257+
slippage = getattr(o, 'slippage', 0) or 0
258+
created = getattr(o, 'created_at', None)
259+
orders.append({
260+
"symbol": getattr(o, 'target_symbol', '—'),
261+
"side": str(getattr(o, 'order_side', '—')),
262+
"type": str(getattr(o, 'order_type', '—')),
263+
"status": str(getattr(o, 'status', '—')),
264+
"price": round(float(price), 4),
265+
"amount": round(float(amount), 6),
266+
"filled": round(float(filled), 6),
267+
"cost": round(float(amount) * float(price), 2),
268+
"fee": round(float(fee), 4),
269+
"fee_rate": round(float(fee_rate), 4),
270+
"slippage": round(float(slippage), 4),
271+
"created": _fmt_date(created),
272+
"window": wname,
273+
})
274+
orders.sort(
275+
key=lambda x: x["created"] if x["created"] != "—" else "",
276+
reverse=True,
277+
)
278+
return orders[:limit]
279+
280+
281+
def _all_positions(bt, window=None):
282+
"""Get all positions for a strategy across windows."""
283+
positions = []
284+
for run in bt.get_all_backtest_runs():
285+
if window and run.backtest_date_range_name != window:
286+
continue
287+
wname = run.backtest_date_range_name or "—"
288+
for p in (run.positions or []):
289+
amount = getattr(p, 'amount', 0) or 0
290+
cost = getattr(p, 'cost', 0) or 0
291+
positions.append({
292+
"symbol": getattr(p, 'symbol', '—'),
293+
"amount": round(float(amount), 6),
294+
"cost": round(float(cost), 2),
295+
"window": wname,
296+
})
297+
return positions
298+
299+
244300
def _full_analysis(backtests, tags=None):
245301
"""Generate a complete analysis markdown document."""
246302
has_tags = tags and any(tags.values())
@@ -1214,6 +1270,66 @@ def _get_tools(self):
12141270
"required": ["strategy_id"],
12151271
},
12161272
},
1273+
{
1274+
"name": "get_orders",
1275+
"description": (
1276+
"Get all orders for a strategy — symbol, side, type, "
1277+
"status, price, amount, filled, cost, fee, fee_rate, "
1278+
"slippage, and creation date. Optionally filter by "
1279+
"window. Sorted by date (newest first)."
1280+
),
1281+
"inputSchema": {
1282+
"type": "object",
1283+
"properties": {
1284+
"strategy_id": {
1285+
"type": "string",
1286+
"description": (
1287+
"The algorithm_id of the strategy"
1288+
),
1289+
},
1290+
"window": {
1291+
"type": "string",
1292+
"description": (
1293+
"Optional: specific window name "
1294+
"to filter by"
1295+
),
1296+
},
1297+
"limit": {
1298+
"type": "integer",
1299+
"description": (
1300+
"Max orders to return (default 50)"
1301+
),
1302+
},
1303+
},
1304+
"required": ["strategy_id"],
1305+
},
1306+
},
1307+
{
1308+
"name": "get_positions",
1309+
"description": (
1310+
"Get all positions for a strategy — symbol, amount, "
1311+
"and cost. Optionally filter by window."
1312+
),
1313+
"inputSchema": {
1314+
"type": "object",
1315+
"properties": {
1316+
"strategy_id": {
1317+
"type": "string",
1318+
"description": (
1319+
"The algorithm_id of the strategy"
1320+
),
1321+
},
1322+
"window": {
1323+
"type": "string",
1324+
"description": (
1325+
"Optional: specific window name "
1326+
"to filter by"
1327+
),
1328+
},
1329+
},
1330+
"required": ["strategy_id"],
1331+
},
1332+
},
12171333
{
12181334
"name": "get_equity_curve",
12191335
"description": (
@@ -2018,6 +2134,72 @@ def _handle_tool_call(self, name, arguments):
20182134
)
20192135
return md
20202136

2137+
elif name == "get_orders":
2138+
sid = arguments.get("strategy_id", "")
2139+
bt = self._bt_map.get(sid)
2140+
if not bt:
2141+
return f"Strategy '{sid}' not found."
2142+
window = arguments.get("window")
2143+
limit = arguments.get("limit", 50)
2144+
orders = _all_orders(bt, window=window, limit=limit)
2145+
if not orders:
2146+
return "No orders found."
2147+
md = f"# Orders for {sid}\n\n"
2148+
md += (
2149+
"| Symbol | Side | Type | Status"
2150+
" | Price | Amount | Filled"
2151+
" | Cost | Fee | Fee Rate"
2152+
" | Slippage | Created | Window |\n"
2153+
)
2154+
md += (
2155+
"|--------|------|------|--------"
2156+
"|-------|--------|--------"
2157+
"|------|-----|----------"
2158+
"|----------|---------|--------|\n"
2159+
)
2160+
for o in orders:
2161+
md += (
2162+
f"| {o['symbol']} "
2163+
f"| {o['side']} "
2164+
f"| {o['type']} "
2165+
f"| {o['status']} "
2166+
f"| {o['price']} "
2167+
f"| {o['amount']} "
2168+
f"| {o['filled']} "
2169+
f"| {o['cost']} "
2170+
f"| {o['fee']} "
2171+
f"| {o['fee_rate']} "
2172+
f"| {o['slippage']} "
2173+
f"| {o['created']} "
2174+
f"| {o['window']} |\n"
2175+
)
2176+
md += (
2177+
f"\n*Showing {len(orders)} orders"
2178+
f" (limit: {limit})*\n"
2179+
)
2180+
return md
2181+
2182+
elif name == "get_positions":
2183+
sid = arguments.get("strategy_id", "")
2184+
bt = self._bt_map.get(sid)
2185+
if not bt:
2186+
return f"Strategy '{sid}' not found."
2187+
window = arguments.get("window")
2188+
positions = _all_positions(bt, window=window)
2189+
if not positions:
2190+
return "No positions found."
2191+
md = f"# Positions for {sid}\n\n"
2192+
md += "| Symbol | Amount | Cost | Window |\n"
2193+
md += "|--------|--------|------|--------|\n"
2194+
for p in positions:
2195+
md += (
2196+
f"| {p['symbol']} "
2197+
f"| {p['amount']} "
2198+
f"| {p['cost']} "
2199+
f"| {p['window']} |\n"
2200+
)
2201+
return md
2202+
20212203
elif name == "get_equity_curve":
20222204
window = arguments.get("window")
20232205
sids = self._resolve_sids(arguments)

0 commit comments

Comments
 (0)